@snap/camera-kit
Version:
Camera Kit Web
177 lines • 11.2 kB
TypeScript
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