UNPKG

@snap/camera-kit

Version:
177 lines 11.2 kB
import type { Memoized } from "../common/memoize"; import { PartialContainer } from "./PartialContainer"; import type { AddService, InjectableFunction, ValidTokens } from "./types"; type MaybeMemoizedFactories<Services> = { [K in keyof Services]: ((c: Container<Services>) => Services[K]) | Memoized<(c: Container<Services>) => Services[K]>; }; export type Factories<Services> = { [K in keyof Services]: Memoized<(c: Container<any>) => Services[K]>; }; export declare const CONTAINER = "$container"; export type ContainerToken = typeof CONTAINER; /** * A Container of values, indexed each by a unique token, which can be used throughout CameraKit. This is how CameraKit * implements simple dependency injection. * * Dependency injection is a way to decouple the *use* of a dependency from the *creation* of that dependency. This * improves modularity and re-usability, since components only care about the *interfaces* of dependencies (since that * determines their use) and not about their concrete creation. New implementations of a particular dependency may be * provided without the need to change any of the consumers of that dependency. * * There are a few commonly-used terms used when talking about dependency injection: * * - Container (or Injector): Maintains a registry of all available Services and understands how to create them. * - Service: Anything that can be provided by the Container is called a Service – this can be a value of any type. * - Token: Each Service is associated with a unique name, or Token. In order to obtain a Service from the Container, * the consumer must provide the Token corresponding to that Service. * - InjectableFunction: Services are created by InjectableFunctions. When adding a Service to a Container, the * Service provider gives the Container a InjectableFunction which, when called will return the Service. These * InjectableFunctions may themselves use other Services, which will be passed to them as arguments. * * Services are, by default, singletons – that is, each call to `get()` a particular Service will return a reference * to the same value. In other words, InjectableFunctions are only invoked once. If multiple instances of a Service are * desired, a new Container can be created using the `copy([Token])` method – passing a Token to this method forces the * new Container to recreate the corresponding Service (the InjectableFunction will be invoked again). We say that the * Service is then "scoped" to the new Container. * * * One common downside of many dependency injection implementations is that the dependency graph formed by the various * Services can only be validated at runtime. That is, if a dependency is missing or a circular dependency is found, the * developer must wait until runtime to discover the error. These errors can often be confusing and hard to debug. * * This implementation eliminates this issue by moving these sorts of errors to compile time. If an unknown dependency * is used in a InjectableFunction, for example, the code simply won't compile. * * To achieve this, we do lose the ability to implicitly define the dependency graph, as is common with many dependency * injection frameworks that employ decorators to define Services and their dependencies. Instead, the dependency graph * must be constructed explicitly, step-by-step, via successive calls to the `provide()` method. This is a suitable * trade-off for CameraKit, as there are a relatively small number of Services. * * Here's a simple example of Container usage: * ```ts * const fooFactory = Injectable('Foo', () => new Foo()) * const barFactory = Injectable('Bar', ['Foo'] as const, (foo: Foo) => new Bar(foo)) * const container = Container.empy() * .provide(fooFactory) * .provide(barFactory) * * const bar: Bar = container.get('Bar') * ``` */ /** @internal */ export declare class Container<Services = {}> { /** * Create a new [Container] by providing a [PartialContainer] that has no dependencies. */ static provides<Services>(container: PartialContainer<Services, {}> | Container<Services>): Container<Services>; /** * Create a new [Container] by providing a Service that has no dependencies. */ static provides<Token extends string, Service>(fn: InjectableFunction<{}, [], Token, Service>): Container<AddService<{}, Token, Service>>; private readonly factories; constructor(factories: MaybeMemoizedFactories<Services>); /** * Create a copy of this Container, optionally providing a list of Services which will be scoped to the copy. * * This can be useful, for example, if different parts of an application wish to use the same Service interface, but * do not want to share a reference to same Service instance. * * Say we have a Service which manages a list of Users. Our application wishes to display two lists of Users, which * may be edited independently. In this case it may be desirable to create a Container for each list component, with * the UserList Service scoped to those Containers – that way, each list component gets a unique copy of the * UserList Service that can be edited independently of the other. * * @param scopedServices A list of Tokens identifying Services which will be scoped to the new Container – that is, * if those Services had already been created by the source Container, they will be re-created by their Factory * functions when provided by the new Container. * @returns A new copy of this Container, sharing all of this Container's Services. Services corresponding to any * Tokens passed to this method will be re-created by the new Container (i.e. they become "scoped" to the new * Container). */ copy<Tokens extends readonly (keyof Services)[]>(scopedServices?: Tokens): Container<Services>; /** * Gets a reference to this Container. * * @param token The CONTAINER token. * @returns This Container. */ get(token: ContainerToken): this; /** * Get a specific Service provided by this Container. * * @param token A unique string corresponding to a Service * @returns A Service corresponding to the given Token. */ get<Token extends keyof Services>(token: Token): Services[Token]; /** * Run the services in this [PartialContainer]. "Run" simply means that [Container::get] will be called for each * Service, which invokes that Service's factory function, creating the Service. * * This may be useful e.g. if services need to initialize themselves, since generally a Service factory is only * invoked when the Service is needed. * * Note this method cannot be used to add services to a Container. – that is, calling this method does not provide * the services in a new Container. * * @param container Optionally provide a [PartialContainer], which will be used as a filter – the only services * from *this* container that will run are those with a token that is also present in this PartialContainer. * @returns No mutation is done to the Container, it is returned as-is (convenient for chaining). */ run<AdditionalServices, Dependencies, FulfilledDependencies extends Dependencies>(this: Container<FulfilledDependencies>, container: PartialContainer<AdditionalServices, Dependencies>): this; /** * Run the given Service. "Run" simply means that [Container::get] will be called for this Service, which invokes * the Service's factory function, creating the Service. * * This may be useful e.g. if services need to initialize themselves, since generally a Service factory is only * invoked when the Service is needed. * * Note this method cannot be used to add services to a Container. – that is, calling this method does not provide * the services in a new Container. * * @param fn Optionally provide an [InjectableFunction], which will be used as a filter – the only services * from *this* container that will run are those with a token that is also present in this PartialContainer. * @returns No mutation is done to the Container, it is returned as-is (convenient for chaining). */ run<Token extends string, Tokens extends readonly ValidTokens<Services>[], Service>(fn: InjectableFunction<Services, Tokens, Token, Service>): this; /** * Create a new Container from this Container with additional services from a given [PartialContainer]. * * Services in the provided PartialContainer take precedence if there are service token conflicts. * * Services from the provided PartialContainer become scoped to the new Container – that is, if PartialContainer A * is provided to Container X and Container Y, each resultant Container will contain its own copy of the services * from PartialContainer A. * * @param container A [PartialContainer] providing additional services. */ provides<AdditionalServices, Dependencies, FulfilledDependencies extends Dependencies>(this: Container<FulfilledDependencies>, container: PartialContainer<AdditionalServices, Dependencies>): Container<Services & AdditionalServices>; /** * Creates a new Container from this Container with additional services from another Container. * * Services in the provided PartialContainer take precedence if there are service token conflicts. * * Services from the provided Container become scoped to both Containers (the one from which they were provided * and the new Container returned by this method) - that is, if Container A is provided to Container B, * they will share the same instances of any Services provided by Container A. * If Container B should re-create new instances of the Services from Container A, * Container A must first be copied before providing it here. * * @param container A [Container] providing additional services. */ provides<AdditionalServices>(container: Container<AdditionalServices>): Container<Services & AdditionalServices>; /** * Create a new Container which provides a Service created by the given [InjectableFunction]. * * The InjectableFunction contains metadata specifying the Token by which the created Service will be known, as well * as an ordered list of Tokens to be resolved and provided to the InjectableFunction as arguments. * * If any of these required dependencies are missing from the Container (or if there is a mismatch between the types * of those dependencies and the arguments of the InjectableFunction), a compiler error will be raised. * * @param fn A factory function, taking dependencies as arguments, which returns the Service. */ provides<Token extends string, Tokens extends readonly ValidTokens<Services>[], Service>(fn: InjectableFunction<Services, Tokens, Token, Service>): Container<AddService<Services, Token, Service>>; private providesService; } export {}; //# sourceMappingURL=Container.d.ts.map