-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from freshgum-bubbles/jsservice-api
Add new JSService API
- Loading branch information
Showing
13 changed files
with
377 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const BUILT_INS = [String, Object, Symbol, Array, Number]; |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { ServiceOptions } from "../interfaces/service-options.interface"; | ||
import { AnyConstructable } from "../types/any-constructable.type"; | ||
import { AnyInjectIdentifier } from "../types/inject-identifier.type"; | ||
import { Service } from "./service.decorator"; | ||
|
||
/** | ||
* Marks class as a service that can be injected using Container. | ||
* Uses the default options, wherein the class can be passed to `.get` and an instance of it will be returned. | ||
* By default, the service shall be registered upon the `defaultContainer` container. | ||
* @experimental | ||
* | ||
* @param dependencies The dependencies to provide upon initialisation of this service. | ||
* These will be provided to the service as arguments to its constructor. | ||
* They must be valid identifiers in the container the service shall be executed under. | ||
* | ||
* @param constructor The constructor of the service to inject. | ||
* | ||
* @returns The constructor. | ||
*/ | ||
export function JSService<T extends AnyConstructable>(dependencies: AnyInjectIdentifier[], constructor: T): T; | ||
|
||
/** | ||
* Marks class as a service that can be injected using Container. | ||
* The options allow customization of how the service is injected. | ||
* By default, the service shall be registered upon the `defaultContainer` container. | ||
* @experimental | ||
* | ||
* @param options The options to use for initialisation of the service. | ||
* Documentation for the options can be found in ServiceOptions. | ||
* | ||
* @param dependencies The dependencies to provide upon initialisation of this service. | ||
* These will be provided to the service as arguments to its constructor. | ||
* They must be valid identifiers in the container the service shall be executed under. | ||
* | ||
* @param constructor The constructor of the service to inject. | ||
* | ||
* @returns The constructor. | ||
*/ | ||
export function JSService<T extends AnyConstructable>(options: Omit<ServiceOptions<T>, 'dependencies'>, dependencies: AnyInjectIdentifier[], constructor: T): T; | ||
|
||
/** | ||
* Marks class as a service that can be injected using Container. | ||
* The options allow customization of how the service is injected. | ||
* By default, the service shall be registered upon the `defaultContainer` container. | ||
* | ||
* @param options The options to use for initialisation of the service. | ||
* Documentation for the options can be found in ServiceOptions. | ||
* The options must also contain the dependencies that the service requires. | ||
* | ||
* If found, the specified dependencies to provide upon initialisation of this service. | ||
* These will be provided to the service as arguments to its constructor. | ||
* They must be valid identifiers in the container the service shall be executed under. | ||
* | ||
* @param constructor The constructor of the service to inject. | ||
* | ||
* @returns The constructor. | ||
*/ | ||
export function JSService<T extends AnyConstructable>(options: ServiceOptions<T> & { dependencies: AnyInjectIdentifier[] }, constructor: T): T; | ||
|
||
export function JSService<T extends AnyConstructable>( | ||
optionsOrDependencies: Omit<ServiceOptions<T>, 'dependencies'> | ServiceOptions<T> | AnyInjectIdentifier[], | ||
dependenciesOrConstructor: AnyInjectIdentifier[] | T, | ||
maybeConstructor?: T | ||
): T { | ||
let constructor!: T; | ||
|
||
if (typeof dependenciesOrConstructor === 'function') { | ||
constructor = dependenciesOrConstructor as T; | ||
Service(optionsOrDependencies as ServiceOptions<T> & { dependencies: AnyInjectIdentifier[] })(constructor); | ||
} else if (maybeConstructor) { | ||
constructor = maybeConstructor; | ||
Service(optionsOrDependencies, dependenciesOrConstructor)(constructor); | ||
} | ||
|
||
if (!constructor) { | ||
throw new Error('The JSService overload was not used correctly.'); | ||
} | ||
|
||
return constructor; | ||
} | ||
|
||
export type JSService<T> = T extends AnyConstructable<infer U> ? U : never; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { CannotInstantiateValueError } from "./cannot-instantiate-value.error"; | ||
|
||
/** | ||
* Thrown when DI encounters a service depending on a built-in type (Number, String) with no factory. | ||
*/ | ||
export class CannotInstantiateBuiltInError extends CannotInstantiateValueError { | ||
get message () { | ||
return super.message + ` If your service requires built-in or unresolvable types, please use a factory.`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { AnyConstructable } from "./any-constructable.type"; | ||
|
||
export type JSService<T extends AnyConstructable> = T; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import Container from '../../src/index'; | ||
import { JSService } from '../../src/decorators/js-service.decorator'; | ||
import { AnyConstructable } from '../types/any-constructable.type'; | ||
|
||
describe('JSService decorator', () => { | ||
beforeEach(() => Container.reset({ strategy: 'resetValue' })); | ||
|
||
type InstanceOf<T> = T extends AnyConstructable<infer U> ? U : never; | ||
|
||
it('should take dependencies and constructor', () => { | ||
type AnotherService = InstanceOf<typeof AnotherService>; | ||
const AnotherService = JSService([], class AnotherService { | ||
getMeaningOfLife () { | ||
return 42; | ||
} | ||
}); | ||
|
||
type MyService = InstanceOf<typeof MyService>; | ||
const MyService = JSService([AnotherService], class MyService { | ||
constructor (public anotherService: AnotherService) { } | ||
}); | ||
|
||
expect(Container.has(MyService)).toBe(true); | ||
expect(Container.has(AnotherService)).toBe(true); | ||
expect(Container.get(MyService).anotherService.getMeaningOfLife()).toStrictEqual(42); | ||
}); | ||
|
||
it('should take options without dependencies, list of dependencies and constructor', () => { | ||
type AnotherService = InstanceOf<typeof AnotherService>; | ||
const AnotherService = JSService([], class AnotherService { | ||
getMeaningOfLife () { | ||
return 42; | ||
} | ||
}); | ||
|
||
type MyService = InstanceOf<typeof MyService>; | ||
const MyService = JSService({ }, [AnotherService], class MyService { | ||
public anotherService: AnotherService; | ||
|
||
constructor (anotherService: AnotherService) { | ||
this.anotherService = anotherService; | ||
} | ||
}); | ||
|
||
expect(Container.has(MyService)).toBe(true); | ||
expect(Container.has(AnotherService)).toBe(true); | ||
expect(Container.get(MyService).anotherService.getMeaningOfLife()).toStrictEqual(42); | ||
}); | ||
|
||
it('should take options with dependencies and constructor', () => { | ||
type AnotherService = InstanceOf<typeof AnotherService>; | ||
const AnotherService = JSService([], class AnotherService { | ||
getMeaningOfLife () { | ||
return 42; | ||
} | ||
}); | ||
|
||
type MyService = InstanceOf<typeof MyService>; | ||
const MyService = JSService({ dependencies: [AnotherService] }, class MyService { | ||
public anotherService: AnotherService; | ||
|
||
constructor (anotherService: AnotherService) { | ||
this.anotherService = anotherService; | ||
} | ||
}); | ||
|
||
expect(Container.has(MyService)).toBe(true); | ||
expect(Container.has(AnotherService)).toBe(true); | ||
expect(Container.get(MyService).anotherService.getMeaningOfLife()).toStrictEqual(42); | ||
}); | ||
|
||
it('should provide a functioning JSService type', () => { | ||
const MyService = JSService([], class MyService { | ||
getOne () { | ||
return 1; | ||
} | ||
}); | ||
|
||
type MyService = JSService<typeof MyService>; | ||
|
||
const myService: MyService = Container.get(MyService); | ||
}); | ||
}); |
Oops, something went wrong.