UNPKG

@furystack/inject

Version:

Dependency Injection framework for FuryStack

77 lines 2.8 kB
/** * Each {@link defineService} call mints a fresh {@link Symbol} as the token's * identity. The symbol's description uses `name` purely for debug/readability; * uniqueness comes from the `Symbol` constructor itself and is independent of * the description. * * Using a plain `Symbol` (not `Symbol.for`) gives two important properties: * * - **Cross-author collision is structurally impossible.** Two packages can * choose the same `name` and still get distinct tokens because each module * evaluation produces its own symbol. * - **Intentional dual-version usage works.** If an application deliberately * depends on two versions of the same library, each version's * `defineService` call runs independently and yields its own token, so both * versions coexist with separate caches. * * The trade-off is that duplicate module instances of a single library version * (a misconfigured `peerDependency`, `npm link`, etc.) produce two tokens and * therefore two cached singletons for what was meant to be one. That failure * mode is rare, obvious in symptoms, and user-fixable — strictly preferable to * the silent cross-author collision that `Symbol.for` would enable. */ const createTokenId = (name) => Symbol(name); /** * Defines a sync service and returns a {@link Token} resolvable via * {@link Injector.get}. * * @example * ```ts * const Counter = defineService({ * name: 'my-app/Counter', * lifetime: 'singleton', * factory: () => { * let value = 0 * return { increment: () => ++value, getValue: () => value } * }, * }) * ``` */ export const defineService = (options) => { return { id: createTokenId(options.name), name: options.name, lifetime: options.lifetime, isAsync: false, factory: options.factory, }; }; /** * Async counterpart of {@link defineService}. The returned token can only be * resolved via {@link Injector.getAsync} — {@link Injector.get} rejects async * tokens at compile time. * * Resolved values are cached after first resolution; concurrent callers share * the same pending promise. */ export const defineServiceAsync = (options) => { return { id: createTokenId(options.name), name: options.name, lifetime: options.lifetime, isAsync: true, factory: options.factory, }; }; export const isToken = (value) => { if (typeof value !== 'object' || value === null) { return false; } const candidate = value; return (typeof candidate.id === 'symbol' && typeof candidate.name === 'string' && typeof candidate.lifetime === 'string' && typeof candidate.isAsync === 'boolean' && typeof candidate.factory === 'function'); }; //# sourceMappingURL=define-service.js.map