From 39eff4817bde0e17f0cb0a4cd8f27f549404c34c Mon Sep 17 00:00:00 2001 From: Dexter Miguel <5452298+divmgl@users.noreply.github.com> Date: Sun, 29 Oct 2023 13:45:21 -0400 Subject: [PATCH] :wrench: --- .../example-fastify/src/SQLiteTaskStore.ts | 14 +- packages/example-fastify/src/Service.ts | 10 +- packages/example-fastify/src/TasksCreator.ts | 4 +- packages/example-fastify/src/index.ts | 3 +- packages/example-hello-world/src/index.ts | 2 +- packages/nwire/README.md | 39 +- packages/nwire/dist/Container.d.ts | 40 ++ packages/nwire/dist/CountingSet.d.ts | 12 + packages/nwire/dist/Singleton.d.ts | 10 + packages/nwire/dist/cjs/index.js | 180 +++++--- packages/nwire/dist/cjs/index.js.map | 8 +- packages/nwire/dist/esm/index.js | 177 +++++--- packages/nwire/dist/esm/index.js.map | 8 +- packages/nwire/dist/index.d.ts | 2 + packages/nwire/dist/pick.d.ts | 1 + packages/nwire/nodemon.json | 5 +- packages/nwire/package.json | 5 +- packages/nwire/src/Container.test.ts | 401 ++++++++++++------ packages/nwire/src/Container.ts | 269 ++++++------ packages/nwire/src/Injected.ts | 5 - packages/nwire/src/Singleton.ts | 31 ++ packages/nwire/src/index.ts | 4 +- packages/nwire/src/pick.ts | 12 + pnpm-lock.yaml | 180 +++++++- 24 files changed, 980 insertions(+), 442 deletions(-) create mode 100644 packages/nwire/dist/Container.d.ts create mode 100644 packages/nwire/dist/CountingSet.d.ts create mode 100644 packages/nwire/dist/Singleton.d.ts create mode 100644 packages/nwire/dist/index.d.ts create mode 100644 packages/nwire/dist/pick.d.ts delete mode 100644 packages/nwire/src/Injected.ts create mode 100644 packages/nwire/src/Singleton.ts create mode 100644 packages/nwire/src/pick.ts diff --git a/packages/example-fastify/src/SQLiteTaskStore.ts b/packages/example-fastify/src/SQLiteTaskStore.ts index d0f1426..65a9d2c 100644 --- a/packages/example-fastify/src/SQLiteTaskStore.ts +++ b/packages/example-fastify/src/SQLiteTaskStore.ts @@ -4,17 +4,13 @@ import { Service } from "./Service" export class SQLiteTaskStore extends Service implements TaskStore { async get(id: number): Promise { - return ( - (await this.context.db.get(`SELECT * FROM tasks WHERE id = ?`, [id])) ?? - null - ) + return (await this.db.get(`SELECT * FROM tasks WHERE id = ?`, [id])) ?? null } async save(title: string): Promise { - const insert = await this.context.db.run( - `INSERT INTO tasks (title) VALUES (?);`, - [title] - ) + const insert = await this.db.run(`INSERT INTO tasks (title) VALUES (?);`, [ + title, + ]) if (!insert.lastID) throw new Error("unable to save task") @@ -22,6 +18,6 @@ export class SQLiteTaskStore extends Service implements TaskStore { } async delete(id: number): Promise { - await this.context.db.run(`DELETE FROM tasks WHERE id = ?`, [id]) + await this.db.run(`DELETE FROM tasks WHERE id = ?`, [id]) } } diff --git a/packages/example-fastify/src/Service.ts b/packages/example-fastify/src/Service.ts index 97e8b60..b0aadfd 100644 --- a/packages/example-fastify/src/Service.ts +++ b/packages/example-fastify/src/Service.ts @@ -1,4 +1,12 @@ import { Injected } from "nwire" import { AppContext } from "./AppContext" -export class Service extends Injected {} +export class Service extends Injected { + get db() { + return this._context.db + } + + get tasks() { + return this._context.tasks + } +} diff --git a/packages/example-fastify/src/TasksCreator.ts b/packages/example-fastify/src/TasksCreator.ts index 925f914..93b97c7 100644 --- a/packages/example-fastify/src/TasksCreator.ts +++ b/packages/example-fastify/src/TasksCreator.ts @@ -3,7 +3,7 @@ import { Service } from "./Service" export class TasksCreator extends Service { async createBasicTasks() { - await this.context.tasks.save("My first test") - await this.context.tasks.save("My second test") + await this.tasks.save("My first test") + await this.tasks.save("My second test") } } diff --git a/packages/example-fastify/src/index.ts b/packages/example-fastify/src/index.ts index 10bbe6e..0e21a58 100644 --- a/packages/example-fastify/src/index.ts +++ b/packages/example-fastify/src/index.ts @@ -6,7 +6,8 @@ start() // Can use top-level `await` in ESM. async function start() { try { - const server = createServer(await createContext()) + const context = await createContext() + const server = createServer(context) server.listen({ port: 3000 }, (err, address) => { if (err) { diff --git a/packages/example-hello-world/src/index.ts b/packages/example-hello-world/src/index.ts index dbfd147..ae41902 100644 --- a/packages/example-hello-world/src/index.ts +++ b/packages/example-hello-world/src/index.ts @@ -7,7 +7,7 @@ type MyTypedContext = { export class MyService extends Injected { helloWorld() { - return this.context.banner + return this._context.banner } } diff --git a/packages/nwire/README.md b/packages/nwire/README.md index b1ab7e9..539d6b1 100644 --- a/packages/nwire/README.md +++ b/packages/nwire/README.md @@ -14,11 +14,12 @@ type MyTypedContext = { export class MyService extends Injected { helloWorld() { - return this.context.banner + return this._context.banner } } -const context = Container.register("banner", () => "Hello world!") +const context = Container.new() + .register("banner", () => "Hello world!") .instance("my", MyService) .context() @@ -52,7 +53,7 @@ const context = container.context() In a majority of cases you'll be creating a single container, registering a bunch of dependencies, and then grabbing the generated `Context`. For this reason we've included static methods that return a new container and are chainable, so you can write your code like this instead: ```tsx -const context = Container +const context = Container.new() .register("prisma", () => new PrismaClient()) .register("redis", () => new Redis()) .context() @@ -79,7 +80,7 @@ Container.register("users", (context) => new UsersService(context)) // => Contai > ⚠️ The `Context` that's sent to the dependency will be fully setup, but this may not match what the compiler sees as TypeScript is only able to gather what's been currently registered. For instance, the following results in a compiler error: ```tsx -const context = Container +const context = Container.new() .register("tasksCreator", (context) => new TasksCreator(context)) // Argument of type '{}' is not assignable to parameter of type 'AppContext'. // Type '{}' is missing the following properties from type 'AppContext': tasks, tasksCreator @@ -90,7 +91,7 @@ const context = Container However, a method is included to avoid this boilerplate altogether: -#### `Container.instance` +#### `Container.singleton` Your goal will often be to simply pass in the fully resolved `Context` to classes. For this reason, `nwire` provides a function that will create a new instance of your class with a fully resolved `Context` whenever the dependency is resolved: @@ -119,8 +120,7 @@ Container.instance("users", UsersService, { cookieSecret: process.env.COOKIE_SEC Sometimes you'll want to group things together within the `Container`. You could technically do this: ```tsx -const context = Container - // +const context = Container.new() .register("services", (context) => ({ users: new UsersService(context), tasks: new TasksService(context), @@ -139,11 +139,9 @@ However, this has a big issue: once you access `service` for the first time you `nwire` provides a solution for this: `Container.group`. `Container.group` creates a nested `Container` that will only resolve when you access properties within it. The nested container will be passed as the first argument to the function you pass in: ```tsx -const context = Container - // +const context = Container.new() .group("services", (services: Container) => services - // .singleton("users", UsersService) .singleton("tasks", TasksService) ) @@ -166,14 +164,18 @@ type AppContext = { context.services.users.findOne("123") ``` +#### `Container.instance` + +An alias for `Container.singleton`. + ### `Context` The `Context` class is the dependency proxy that the `Container` produces. This class allows you to access your dependencies using the names you registered them with: ```tsx -const context = Container +const context = Container.new() .register("users" /** Registry name */, () => new UsersService()) - .context() + .context() // Proxy created at this point const user = await context.users.findOne("123") ``` @@ -201,7 +203,7 @@ export class MyService extends Injected { This class will fit into the `Container.prototype.instance` API: ```tsx -const context = Container +const context = Container.new() .register("banner", () => "Hello world!") .instance("my", MyService) // No type errors .context() @@ -214,7 +216,7 @@ context.my.helloWorld() // => console output: "Hello world!" Creates a new `Context` class. This is the class you're meant to pass around to all of your dependencies. It's responsible for resolving dependencies: ```tsx -const context = Container +const context = Container.new() // ... lots of registrations here .register("users", () => new UsersService()) .context() @@ -236,7 +238,7 @@ export type AppContext = { tasksCreator: TasksCreator } -const context = Container +const context = Container.new() .register("tasksCreator", (context) => new TasksCreator(context)) .register("tasks", (context) => new SQLiteTaskStore(context)) .context() @@ -259,7 +261,7 @@ This will cause the compiler to completely brick in any service that uses the `A You'll also run into issues if you attempt to pass a partial context during registration to constructor: ```tsx -export const context = Container +export const context = Container.new() .register("users", (contextUpToThisPoint) => new UsersService(contextUpToThisPoint)) // Argument of type '{}' is not assignable to parameter of type 'AppContext'. // Type '{}' is missing the following properties from type 'AppContext': users @@ -296,7 +298,7 @@ container.resolve("randomizer").id // => 248 There is currently no API for transient `instance` registrations, so if you do want to create a unique instance on every call you'll need to provide an initial context: ```tsx -const context = Container.build +const context = Container.new() .register("users", (context) => new UsersService(context), { transient: true })) .context() ``` @@ -381,7 +383,8 @@ and when a dependency is needed the framework will resolve it for you. Consider the previous example in `nwire`: ```tsx -const context = Container.singleton("users", UsersService) +const context = Container.new() + .singleton("users", UsersService) .register("prisma", new PrismaClient()) .register("psql", new Postgres()) diff --git a/packages/nwire/dist/Container.d.ts b/packages/nwire/dist/Container.d.ts new file mode 100644 index 0000000..d244d92 --- /dev/null +++ b/packages/nwire/dist/Container.d.ts @@ -0,0 +1,40 @@ +export type Context = { + [key: string]: unknown; +}; +export type Instance = { + new (context: any, ...args: any[]): TValue; +}; +type Flatten = {} & { + [P in keyof T]: T[P]; +}; +type AppendContext = Flatten; +type RegistrationOptions = { + transient?: boolean; +}; +export declare class Container { + private _registry; + private _resolvers; + private _cache; + private _transient; + private _base; + private _rootContainer; + private _parentContainer; + constructor(rootContainer?: Container, _parentContainer?: Container); + get root(): this | Container<{}>; + get parent(): this | Container<{}>; + static new(): Container; + static build(): Container; + base(base: TBase): Container; + createContextProxy(): Context; + context(override?: TOverride | {}): Flatten; + group(key: TNewKey, decorator: (container: Container<{}>) => Container): Container; + singleton(key: TNewKey, ClassConstructor: Instance, ...args: any[]): Container; + instance(key: TNewKey, ClassConstructor: Instance, ...args: any[]): Container; + register(key: TNewKey, resolver: (context: TContext) => TValue, { transient }?: RegistrationOptions): Container>; + unregister(key: TNewKey): Container>; + resolve(key: keyof TContext): TValue; + middleware(middleware: (container: Container) => Container): Container; +} +export {}; diff --git a/packages/nwire/dist/CountingSet.d.ts b/packages/nwire/dist/CountingSet.d.ts new file mode 100644 index 0000000..1c93b20 --- /dev/null +++ b/packages/nwire/dist/CountingSet.d.ts @@ -0,0 +1,12 @@ +export declare class CountingSet { + private readonly _map; + private readonly _set; + add(value: T): this; + delete(value: T): boolean; + has(value: T): boolean; + count(value: T): number; + clear(): void; + get size(): number; + [Symbol.iterator](): Iterator; + forEach(callbackfn: (value: T, value2: T, set: Set) => void, thisArg?: any): void; +} diff --git a/packages/nwire/dist/Singleton.d.ts b/packages/nwire/dist/Singleton.d.ts new file mode 100644 index 0000000..4ef03fd --- /dev/null +++ b/packages/nwire/dist/Singleton.d.ts @@ -0,0 +1,10 @@ +import { Context } from "./Container"; +type PopulatedSingleton = T & { + [key in keyof T]: T[key]; +}; +export declare function WithContextProperties(Base: any): new (context: T) => PopulatedSingleton; +export declare class Singleton { + protected _context: TContext; + constructor(context: TContext); +} +export {}; diff --git a/packages/nwire/dist/cjs/index.js b/packages/nwire/dist/cjs/index.js index 8b140a6..95c7cc3 100644 --- a/packages/nwire/dist/cjs/index.js +++ b/packages/nwire/dist/cjs/index.js @@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru var src_exports = {}; __export(src_exports, { Container: () => Container, - Injected: () => Injected + Singleton: () => Singleton, + WithContextProperties: () => WithContextProperties }); module.exports = __toCommonJS(src_exports); @@ -71,66 +72,120 @@ var CountingSet = class { // src/Container.ts var Container = class _Container { - constructor(_parentContainer) { - this._parentContainer = _parentContainer; - } _registry = /* @__PURE__ */ new Map(); _resolvers = /* @__PURE__ */ new Map(); + _cache = /* @__PURE__ */ new Map(); _transient = /* @__PURE__ */ new Set(); - static build() { + _base = {}; + _rootContainer; + _parentContainer; + constructor(rootContainer, _parentContainer) { + this._rootContainer = rootContainer ?? this; + this._parentContainer = _parentContainer ?? this._rootContainer; + } + get root() { + return this._rootContainer; + } + get parent() { + return this._parentContainer; + } + static new() { return new _Container(); } - copy(rootContext = {}) { - const keys = Array.from(this._resolvers.keys()); - const context = keys.reduce((acc, key) => { - if (rootContext.hasOwnProperty(key)) { - acc[key] = rootContext[key]; - } else if (this._registry.has(key)) { - acc[key] = this._registry.get(key); - } else { - acc[key] = this.resolve(key); - } - return acc; - }, {}); - return { - ...context, - ...rootContext - }; + static build() { + return _Container.new(); + } + base(base) { + this._base = base; + return this; } - context(rootContext = {}, resolved = new CountingSet()) { + createContextProxy() { const cache = {}; + const resolving = new CountingSet(); const handler = { get: (target, key) => { if (cache.hasOwnProperty(key)) return cache[key]; if (target.hasOwnProperty(key)) return target[key]; - return this.resolve(key, resolved); + return resolve(key); }, set: (_target, key, value) => { cache[key] = value; return true; } }; - const proxy = new Proxy(rootContext, handler); + const proxy = new Proxy({}, handler); + const resolve = (key) => { + var _a; + if ((_a = this._base) == null ? void 0 : _a[key]) + return this._base[key]; + const resolver = this._resolvers.get(key); + if (this._registry.has(key)) { + resolving.delete(resolver); + return this._registry.get(key); + } + if (resolving.count(resolver) > 1) { + resolving.delete(resolver); + return this._cache.get(resolver); + } + const value = resolver == null ? void 0 : resolver( + this._rootContainer.context() + ); + resolving.delete(resolver); + if (!this._transient.has(key)) { + this._registry.set(key, value); + this._parentContainer._registry.set(key, value); + this._cache.set(resolver, value); + } + return value; + }; return proxy; } + context(override = {}) { + const keys = Array.from(this._resolvers.keys()); + const proxy = this.createContextProxy(); + const context = keys.reduce( + (acc, key) => { + Object.defineProperty(acc, key, { + get: () => { + return proxy[key]; + }, + enumerable: true + }); + return acc; + }, + { ...this._base } + ); + return Object.assign(context, override); + } // Add a subcontext to a property of this context group(key, decorator) { - const nestedContainer = new _Container(this._parentContainer ?? this); - const value = decorator(nestedContainer).context(); - this.register(key, () => value); + const groupContainer = decorator(new _Container(this._rootContainer, this)); + const groupContext = groupContainer.context(); + this.register(key, () => groupContext); + const grouping = Array.from(groupContainer._resolvers.keys()).reduce( + (acc, key2) => { + return { + ...acc, + get [key2]() { + return groupContext[key2]; + } + }; + }, + {} + ); + this._registry.set(key, grouping); return this; } - static group(key, decorator) { - return _Container.build().group(key, decorator); + singleton(key, ClassConstructor, ...args) { + return this.register( + key, + (context) => new ClassConstructor(context, ...args) + ); } instance(key, ClassConstructor, ...args) { - this.register(key, (context) => new ClassConstructor(context, ...args)); - return this; - } - static instance(key, ClassConstructor, ...args) { - return _Container.build().instance(key, ClassConstructor, ...args); + return this.singleton(key, ClassConstructor, ...args); } register(key, resolver, { transient } = { transient: false }) { if (transient) @@ -138,50 +193,49 @@ var Container = class _Container { this._resolvers.set(key, resolver); return this; } - static register(key, value, options) { - return _Container.build().register(key, value, options); - } unregister(key) { this._resolvers.delete(key); this._registry.delete(key); this._transient.delete(key); return this; } - static unregister(key) { - return _Container.build().unregister(key); - } - resolve(key, resolved = new CountingSet()) { - var _a; - if (this._registry.has(key)) { - return this._registry.get(key); - } + resolve(key) { const resolver = this._resolvers.get(key); if (!resolver) - return void 0; - if (resolved.count(resolver) > 1) - return void 0; - const context = this.context(void 0, resolved.add(resolver)); - const value = resolver( - ((_a = this._parentContainer) == null ? void 0 : _a.context(void 0, resolved)) ?? context, - resolved.add(resolver) - ); - resolved.delete(resolver); - if (!this._transient.has(key)) { - this._registry.set(key, value); - } - return value; + throw new Error(`dependency ${String(key)} not registered`); + return resolver(this._rootContainer.context()); + } + middleware(middleware) { + return middleware(this); } }; -// src/Injected.ts -var Injected = class { - constructor(_context) { - this._context = _context; +// src/Singleton.ts +function WithContextProperties(Base) { + return class extends Base { + constructor(context) { + super(context); + for (const key in context) { + Object.defineProperty(this, key, { + get: function() { + return context[key]; + }, + enumerable: true + }); + } + } + }; +} +var Singleton = class { + _context; + constructor(context) { + this._context = context; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Container, - Injected + Singleton, + WithContextProperties }); //# sourceMappingURL=index.js.map diff --git a/packages/nwire/dist/cjs/index.js.map b/packages/nwire/dist/cjs/index.js.map index e60d1d2..400850d 100644 --- a/packages/nwire/dist/cjs/index.js.map +++ b/packages/nwire/dist/cjs/index.js.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../../src/index.ts", "../../src/CountingSet.ts", "../../src/Container.ts", "../../src/Injected.ts"], - "sourcesContent": ["export { Container, Instance } from \"./Container\"\nexport { Injected } from \"./Injected\"\n", "export class CountingSet {\n private readonly _map = new Map()\n private readonly _set = new Set()\n\n add(value: T): this {\n const count = this._map.get(value) || 0\n this._map.set(value, count + 1)\n this._set.add(value)\n return this\n }\n\n delete(value: T): boolean {\n if (this._map.has(value)) {\n const count = this._map.get(value)!\n if (count > 1) {\n this._map.set(value, count - 1)\n } else {\n this._map.delete(value)\n this._set.delete(value)\n }\n return true\n }\n return false\n }\n\n has(value: T): boolean {\n return this._set.has(value)\n }\n\n count(value: T): number {\n return this._map.get(value) || 0\n }\n\n clear(): void {\n this._map.clear()\n this._set.clear()\n }\n\n get size(): number {\n return this._set.size\n }\n\n [Symbol.iterator](): Iterator {\n return this._set[Symbol.iterator]()\n }\n\n forEach(\n callbackfn: (value: T, value2: T, set: Set) => void,\n thisArg?: any\n ): void {\n this._set.forEach(callbackfn, thisArg)\n }\n}\n", "import { CountingSet } from \"./CountingSet\"\n\nexport type Context = {\n [key: string]: unknown\n}\n\nexport type Instance = {\n new (context: any, ...args: any[]): TValue\n}\n\ntype Flatten = {} & { [P in keyof T]: T[P] }\n\ntype MergeContext = Flatten<\n TExisting & {\n [P in TKey]: TValue\n }\n>\n\ntype RegistrationOptions = {\n transient?: boolean\n}\n\nexport class Container {\n private _registry: Map = new Map()\n private _resolvers: Map<\n string,\n (context: TContext, resolved: CountingSet) => unknown\n > = new Map unknown>()\n private _transient: Set = new Set()\n\n constructor(private _parentContainer?: Container) {}\n\n static build(): Container {\n return new Container()\n }\n\n copy(rootContext: Context = {}): TContext {\n // Get all of the keys in the map\n const keys = Array.from(this._resolvers.keys())\n\n // Create an object that either has the value from the root context, the value from the registry\n // or gets the value from the map's generator.\n const context = keys.reduce((acc: Record, key) => {\n if (rootContext.hasOwnProperty(key)) {\n acc[key] = rootContext[key]\n } else if (this._registry.has(key)) {\n acc[key] = this._registry.get(key)\n } else {\n acc[key] = this.resolve(key)\n }\n\n return acc\n }, {}) as TContext\n\n return {\n ...context,\n ...rootContext,\n } as TContext\n }\n\n context(\n rootContext: Context = {},\n resolved: CountingSet = new CountingSet()\n ): TWriteContext {\n const cache: Record = {}\n\n const handler = {\n get: (target: TContext, key: string) => {\n if (cache.hasOwnProperty(key)) return cache[key]\n if (target.hasOwnProperty(key)) return target[key]\n return this.resolve(key, resolved)\n },\n set: (_target: Context, key: string, value: unknown) => {\n cache[key] = value\n return true\n },\n }\n\n const proxy = new Proxy(rootContext as TContext, handler)\n\n return proxy as unknown as TWriteContext\n }\n\n // Add a subcontext to a property of this context\n group(\n key: TNewKey,\n decorator: (container: Container) => Container\n ): Container> {\n const nestedContainer = new Container(this._parentContainer ?? this)\n const value = decorator(nestedContainer).context()\n this.register(key, () => value)\n return this as any\n }\n\n static group(\n key: TNewKey,\n decorator: (container: Container) => Container\n ): Container> {\n return Container.build().group(key, decorator) as any\n }\n\n instance(\n key: TNewKey,\n ClassConstructor: Instance,\n ...args: any[]\n ): Container> {\n this.register(key, (context) => new ClassConstructor(context, ...args))\n return this as any\n }\n\n static instance(\n key: TNewKey,\n ClassConstructor: Instance,\n ...args: any[]\n ): Container> {\n return Container.build().instance(key, ClassConstructor, ...args) as any\n }\n\n register(\n key: TNewKey,\n resolver: (context: TContext, resolvedSet: CountingSet) => TValue,\n { transient }: RegistrationOptions = { transient: false }\n ): Container> {\n if (transient) this._transient.add(key)\n this._resolvers.set(key, resolver)\n return this as any\n }\n\n static register(\n key: TNewKey,\n value: (context: Context) => TValue,\n options?: RegistrationOptions\n ): Container> {\n return Container.build().register(key, value, options) as any\n }\n\n unregister(\n key: TNewKey\n ): Container> {\n this._resolvers.delete(key)\n this._registry.delete(key)\n this._transient.delete(key)\n\n return this as any\n }\n\n static unregister(\n key: TNewKey\n ): Container> {\n return Container.build().unregister(key) as any\n }\n\n resolve(\n key: keyof TContext,\n resolved: CountingSet = new CountingSet()\n ): T {\n if (this._registry.has(key as string)) {\n return this._registry.get(key as string) as unknown as T\n }\n\n const resolver = this._resolvers.get(key as string)!\n if (!resolver) return undefined as unknown as T\n if (resolved.count(resolver) > 1) return undefined as unknown as T\n\n const context = this.context(undefined, resolved.add(resolver))\n\n const value = resolver(\n this._parentContainer?.context(undefined, resolved) ?? context,\n resolved.add(resolver)\n ) as unknown as T\n\n resolved.delete(resolver)\n\n if (!this._transient.has(key as string)) {\n this._registry.set(key as string, value)\n }\n\n return value\n }\n}\n\ntype Serializable =\n | null\n | string\n | number\n | boolean\n | undefined\n | { [key: string]: Serializable }\n | Serializable[]\n\nfunction handleCircularReferences(\n obj: Serializable,\n path: Serializable[] = []\n): Serializable {\n if (obj === null) return null\n if (typeof obj !== \"object\") return obj\n\n const occurrence = path.filter((p) => p === obj).length\n\n // If this object appears more than once in the current path, it's a circular reference.\n if (occurrence > 1) {\n return undefined\n }\n\n // path.push(obj)\n\n let result: Serializable\n if (Array.isArray(obj)) {\n result = obj.map((item) =>\n handleCircularReferences(item, path.slice())\n ) as Serializable[]\n } else {\n result = {}\n for (let key in obj) {\n ;(result as any)[key] = handleCircularReferences(\n (obj as any)[key],\n path.slice()\n )\n }\n }\n\n path.pop()\n\n return result\n}\n", "import { Context } from \"./Container\"\n\nexport class Injected {\n constructor(protected _context: TContext) {}\n}\n"], - "mappings": "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", - "names": [] + "sources": ["../../src/index.ts", "../../src/CountingSet.ts", "../../src/Container.ts", "../../src/Singleton.ts"], + "sourcesContent": ["export { Container, Instance, Context } from \"./Container\"\nexport { Singleton, WithContextProperties } from \"./Singleton\"\n", "export class CountingSet {\n private readonly _map = new Map()\n private readonly _set = new Set()\n\n add(value: T): this {\n const count = this._map.get(value) || 0\n this._map.set(value, count + 1)\n this._set.add(value)\n return this\n }\n\n delete(value: T): boolean {\n if (this._map.has(value)) {\n const count = this._map.get(value)!\n if (count > 1) {\n this._map.set(value, count - 1)\n } else {\n this._map.delete(value)\n this._set.delete(value)\n }\n return true\n }\n return false\n }\n\n has(value: T): boolean {\n return this._set.has(value)\n }\n\n count(value: T): number {\n return this._map.get(value) || 0\n }\n\n clear(): void {\n this._map.clear()\n this._set.clear()\n }\n\n get size(): number {\n return this._set.size\n }\n\n [Symbol.iterator](): Iterator {\n return this._set[Symbol.iterator]()\n }\n\n forEach(\n callbackfn: (value: T, value2: T, set: Set) => void,\n thisArg?: any\n ): void {\n this._set.forEach(callbackfn, thisArg)\n }\n}\n", "import { CountingSet } from \"./CountingSet\"\n\nexport type Context = {\n [key: string]: unknown\n}\n\nexport type Instance = {\n new (context: any, ...args: any[]): TValue\n}\n\ntype Flatten = {} & { [P in keyof T]: T[P] }\n\ntype AppendContext = Flatten<\n TExisting & {\n [P in TKey]: TValue\n }\n>\n\ntype RegistrationOptions = {\n transient?: boolean\n}\n\nexport class Container {\n private _registry: Map = new Map()\n private _resolvers: Map unknown> = new Map<\n string,\n (context: TContext) => unknown\n >()\n private _cache: Map<(context: TContext) => unknown, unknown> = new Map()\n private _transient: Set = new Set()\n private _base: Record = {}\n\n private _rootContainer: Container | this\n private _parentContainer: Container | this\n\n constructor(rootContainer?: Container, _parentContainer?: Container) {\n this._rootContainer = rootContainer ?? this\n this._parentContainer = _parentContainer ?? this._rootContainer\n }\n\n get root() {\n return this._rootContainer\n }\n\n get parent() {\n return this._parentContainer\n }\n\n static new(): Container {\n return new Container()\n }\n\n static build() {\n return Container.new()\n }\n\n base(base: TBase): Container {\n this._base = base\n return this as any\n }\n\n createContextProxy() {\n const cache: Record = {}\n const resolving = new CountingSet()\n\n const handler = {\n get: (target: TContext, key: string) => {\n if (cache.hasOwnProperty(key)) return cache[key]\n if (target.hasOwnProperty(key)) return target[key]\n\n return resolve(key)\n },\n set: (_target: Context, key: string, value: unknown) => {\n cache[key] = value\n return true\n },\n }\n\n const proxy = new Proxy({}, handler)\n\n const resolve = (key: keyof TContext) => {\n if (this._base?.[key as string])\n return this._base[key as string] as TValue\n\n const resolver = this._resolvers.get(key as string)!\n\n if (this._registry.has(key as string)) {\n resolving.delete(resolver)\n return this._registry.get(key as string) as unknown as TValue\n }\n\n if (resolving.count(resolver) > 1) {\n resolving.delete(resolver)\n return this._cache.get(resolver) as unknown as TValue\n }\n\n const value = resolver?.(\n this._rootContainer.context() as unknown as TContext\n ) as unknown as TValue\n\n resolving.delete(resolver)\n\n if (!this._transient.has(key as string)) {\n this._registry.set(key as string, value)\n this._parentContainer._registry.set(key as string, value)\n this._cache.set(resolver, value)\n }\n\n return value\n }\n\n return proxy\n }\n\n context<\n TWriteContext extends Context = TContext,\n TOverride extends Context = {}\n >(override: TOverride | {} = {}): Flatten {\n // Get all of the keys for the resolvers in this container\n const keys = Array.from(this._resolvers.keys())\n\n const proxy = this.createContextProxy()\n\n const context = keys.reduce(\n (acc, key) => {\n Object.defineProperty(acc, key, {\n get: () => {\n return proxy[key as keyof typeof proxy]\n },\n enumerable: true,\n })\n\n return acc\n },\n { ...this._base } as Flatten\n )\n\n return Object.assign(context, override)\n }\n\n // Add a subcontext to a property of this context\n group(\n key: TNewKey,\n decorator: (container: Container<{}>) => Container\n ) {\n // @ts-expect-error\n // Create a new container for the group and set this container as the parent.\n const groupContainer = decorator(new Container(this._rootContainer, this))\n\n const groupContext = groupContainer.context()\n this.register(key, () => groupContext)\n\n const grouping = Array.from(groupContainer._resolvers.keys()).reduce(\n (acc, key) => {\n return {\n ...acc,\n get [key]() {\n return groupContext[key]\n },\n }\n },\n {} as TNewContext\n )\n\n this._registry.set(key as string, grouping)\n\n return this as Container\n }\n\n singleton(\n key: TNewKey,\n ClassConstructor: Instance,\n ...args: any[]\n ) {\n return this.register(\n key,\n (context) => new ClassConstructor(context, ...args)\n )\n }\n\n instance(\n key: TNewKey,\n ClassConstructor: Instance,\n ...args: any[]\n ) {\n return this.singleton(key, ClassConstructor, ...args)\n }\n\n register(\n key: TNewKey,\n resolver: (context: TContext) => TValue,\n { transient }: RegistrationOptions = { transient: false }\n ): Container> {\n if (transient) this._transient.add(key)\n this._resolvers.set(key, resolver)\n return this as any\n }\n\n unregister(\n key: TNewKey\n ): Container> {\n this._resolvers.delete(key)\n this._registry.delete(key)\n this._transient.delete(key)\n\n return this as any\n }\n\n resolve(key: keyof TContext): TValue {\n const resolver = this._resolvers.get(key as string)\n if (!resolver) throw new Error(`dependency ${String(key)} not registered`)\n return resolver(this._rootContainer.context() as TContext) as TValue\n }\n\n middleware(\n middleware: (container: Container) => Container\n ) {\n return middleware(this)\n }\n}\n", "import { Context } from \"./Container\"\n\ntype PopulatedSingleton = T & { [key in keyof T]: T[key] }\n\n// Mixin to add context-based properties to a class\nexport function WithContextProperties(\n Base: any\n): new (context: T) => PopulatedSingleton {\n return class extends Base {\n constructor(context: T) {\n super(context)\n\n for (const key in context) {\n Object.defineProperty(this, key, {\n get: function () {\n return context[key]\n },\n enumerable: true,\n })\n }\n }\n } as new (context: T) => PopulatedSingleton\n}\n\nexport class Singleton {\n protected _context: TContext\n\n constructor(context: TContext) {\n this._context = context\n }\n}\n"], + "mappings": "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", + "names": ["key"] } diff --git a/packages/nwire/dist/esm/index.js b/packages/nwire/dist/esm/index.js index f92f924..f22aac4 100644 --- a/packages/nwire/dist/esm/index.js +++ b/packages/nwire/dist/esm/index.js @@ -44,66 +44,120 @@ var CountingSet = class { // src/Container.ts var Container = class _Container { - constructor(_parentContainer) { - this._parentContainer = _parentContainer; - } _registry = /* @__PURE__ */ new Map(); _resolvers = /* @__PURE__ */ new Map(); + _cache = /* @__PURE__ */ new Map(); _transient = /* @__PURE__ */ new Set(); - static build() { + _base = {}; + _rootContainer; + _parentContainer; + constructor(rootContainer, _parentContainer) { + this._rootContainer = rootContainer ?? this; + this._parentContainer = _parentContainer ?? this._rootContainer; + } + get root() { + return this._rootContainer; + } + get parent() { + return this._parentContainer; + } + static new() { return new _Container(); } - copy(rootContext = {}) { - const keys = Array.from(this._resolvers.keys()); - const context = keys.reduce((acc, key) => { - if (rootContext.hasOwnProperty(key)) { - acc[key] = rootContext[key]; - } else if (this._registry.has(key)) { - acc[key] = this._registry.get(key); - } else { - acc[key] = this.resolve(key); - } - return acc; - }, {}); - return { - ...context, - ...rootContext - }; + static build() { + return _Container.new(); + } + base(base) { + this._base = base; + return this; } - context(rootContext = {}, resolved = new CountingSet()) { + createContextProxy() { const cache = {}; + const resolving = new CountingSet(); const handler = { get: (target, key) => { if (cache.hasOwnProperty(key)) return cache[key]; if (target.hasOwnProperty(key)) return target[key]; - return this.resolve(key, resolved); + return resolve(key); }, set: (_target, key, value) => { cache[key] = value; return true; } }; - const proxy = new Proxy(rootContext, handler); + const proxy = new Proxy({}, handler); + const resolve = (key) => { + var _a; + if ((_a = this._base) == null ? void 0 : _a[key]) + return this._base[key]; + const resolver = this._resolvers.get(key); + if (this._registry.has(key)) { + resolving.delete(resolver); + return this._registry.get(key); + } + if (resolving.count(resolver) > 1) { + resolving.delete(resolver); + return this._cache.get(resolver); + } + const value = resolver == null ? void 0 : resolver( + this._rootContainer.context() + ); + resolving.delete(resolver); + if (!this._transient.has(key)) { + this._registry.set(key, value); + this._parentContainer._registry.set(key, value); + this._cache.set(resolver, value); + } + return value; + }; return proxy; } + context(override = {}) { + const keys = Array.from(this._resolvers.keys()); + const proxy = this.createContextProxy(); + const context = keys.reduce( + (acc, key) => { + Object.defineProperty(acc, key, { + get: () => { + return proxy[key]; + }, + enumerable: true + }); + return acc; + }, + { ...this._base } + ); + return Object.assign(context, override); + } // Add a subcontext to a property of this context group(key, decorator) { - const nestedContainer = new _Container(this._parentContainer ?? this); - const value = decorator(nestedContainer).context(); - this.register(key, () => value); + const groupContainer = decorator(new _Container(this._rootContainer, this)); + const groupContext = groupContainer.context(); + this.register(key, () => groupContext); + const grouping = Array.from(groupContainer._resolvers.keys()).reduce( + (acc, key2) => { + return { + ...acc, + get [key2]() { + return groupContext[key2]; + } + }; + }, + {} + ); + this._registry.set(key, grouping); return this; } - static group(key, decorator) { - return _Container.build().group(key, decorator); + singleton(key, ClassConstructor, ...args) { + return this.register( + key, + (context) => new ClassConstructor(context, ...args) + ); } instance(key, ClassConstructor, ...args) { - this.register(key, (context) => new ClassConstructor(context, ...args)); - return this; - } - static instance(key, ClassConstructor, ...args) { - return _Container.build().instance(key, ClassConstructor, ...args); + return this.singleton(key, ClassConstructor, ...args); } register(key, resolver, { transient } = { transient: false }) { if (transient) @@ -111,49 +165,48 @@ var Container = class _Container { this._resolvers.set(key, resolver); return this; } - static register(key, value, options) { - return _Container.build().register(key, value, options); - } unregister(key) { this._resolvers.delete(key); this._registry.delete(key); this._transient.delete(key); return this; } - static unregister(key) { - return _Container.build().unregister(key); - } - resolve(key, resolved = new CountingSet()) { - var _a; - if (this._registry.has(key)) { - return this._registry.get(key); - } + resolve(key) { const resolver = this._resolvers.get(key); if (!resolver) - return void 0; - if (resolved.count(resolver) > 1) - return void 0; - const context = this.context(void 0, resolved.add(resolver)); - const value = resolver( - ((_a = this._parentContainer) == null ? void 0 : _a.context(void 0, resolved)) ?? context, - resolved.add(resolver) - ); - resolved.delete(resolver); - if (!this._transient.has(key)) { - this._registry.set(key, value); - } - return value; + throw new Error(`dependency ${String(key)} not registered`); + return resolver(this._rootContainer.context()); + } + middleware(middleware) { + return middleware(this); } }; -// src/Injected.ts -var Injected = class { - constructor(_context) { - this._context = _context; +// src/Singleton.ts +function WithContextProperties(Base) { + return class extends Base { + constructor(context) { + super(context); + for (const key in context) { + Object.defineProperty(this, key, { + get: function() { + return context[key]; + }, + enumerable: true + }); + } + } + }; +} +var Singleton = class { + _context; + constructor(context) { + this._context = context; } }; export { Container, - Injected + Singleton, + WithContextProperties }; //# sourceMappingURL=index.js.map diff --git a/packages/nwire/dist/esm/index.js.map b/packages/nwire/dist/esm/index.js.map index efbbe43..27d266f 100644 --- a/packages/nwire/dist/esm/index.js.map +++ b/packages/nwire/dist/esm/index.js.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../../src/CountingSet.ts", "../../src/Container.ts", "../../src/Injected.ts"], - "sourcesContent": ["export class CountingSet {\n private readonly _map = new Map()\n private readonly _set = new Set()\n\n add(value: T): this {\n const count = this._map.get(value) || 0\n this._map.set(value, count + 1)\n this._set.add(value)\n return this\n }\n\n delete(value: T): boolean {\n if (this._map.has(value)) {\n const count = this._map.get(value)!\n if (count > 1) {\n this._map.set(value, count - 1)\n } else {\n this._map.delete(value)\n this._set.delete(value)\n }\n return true\n }\n return false\n }\n\n has(value: T): boolean {\n return this._set.has(value)\n }\n\n count(value: T): number {\n return this._map.get(value) || 0\n }\n\n clear(): void {\n this._map.clear()\n this._set.clear()\n }\n\n get size(): number {\n return this._set.size\n }\n\n [Symbol.iterator](): Iterator {\n return this._set[Symbol.iterator]()\n }\n\n forEach(\n callbackfn: (value: T, value2: T, set: Set) => void,\n thisArg?: any\n ): void {\n this._set.forEach(callbackfn, thisArg)\n }\n}\n", "import { CountingSet } from \"./CountingSet\"\n\nexport type Context = {\n [key: string]: unknown\n}\n\nexport type Instance = {\n new (context: any, ...args: any[]): TValue\n}\n\ntype Flatten = {} & { [P in keyof T]: T[P] }\n\ntype MergeContext = Flatten<\n TExisting & {\n [P in TKey]: TValue\n }\n>\n\ntype RegistrationOptions = {\n transient?: boolean\n}\n\nexport class Container {\n private _registry: Map = new Map()\n private _resolvers: Map<\n string,\n (context: TContext, resolved: CountingSet) => unknown\n > = new Map unknown>()\n private _transient: Set = new Set()\n\n constructor(private _parentContainer?: Container) {}\n\n static build(): Container {\n return new Container()\n }\n\n copy(rootContext: Context = {}): TContext {\n // Get all of the keys in the map\n const keys = Array.from(this._resolvers.keys())\n\n // Create an object that either has the value from the root context, the value from the registry\n // or gets the value from the map's generator.\n const context = keys.reduce((acc: Record, key) => {\n if (rootContext.hasOwnProperty(key)) {\n acc[key] = rootContext[key]\n } else if (this._registry.has(key)) {\n acc[key] = this._registry.get(key)\n } else {\n acc[key] = this.resolve(key)\n }\n\n return acc\n }, {}) as TContext\n\n return {\n ...context,\n ...rootContext,\n } as TContext\n }\n\n context(\n rootContext: Context = {},\n resolved: CountingSet = new CountingSet()\n ): TWriteContext {\n const cache: Record = {}\n\n const handler = {\n get: (target: TContext, key: string) => {\n if (cache.hasOwnProperty(key)) return cache[key]\n if (target.hasOwnProperty(key)) return target[key]\n return this.resolve(key, resolved)\n },\n set: (_target: Context, key: string, value: unknown) => {\n cache[key] = value\n return true\n },\n }\n\n const proxy = new Proxy(rootContext as TContext, handler)\n\n return proxy as unknown as TWriteContext\n }\n\n // Add a subcontext to a property of this context\n group(\n key: TNewKey,\n decorator: (container: Container) => Container\n ): Container> {\n const nestedContainer = new Container(this._parentContainer ?? this)\n const value = decorator(nestedContainer).context()\n this.register(key, () => value)\n return this as any\n }\n\n static group(\n key: TNewKey,\n decorator: (container: Container) => Container\n ): Container> {\n return Container.build().group(key, decorator) as any\n }\n\n instance(\n key: TNewKey,\n ClassConstructor: Instance,\n ...args: any[]\n ): Container> {\n this.register(key, (context) => new ClassConstructor(context, ...args))\n return this as any\n }\n\n static instance(\n key: TNewKey,\n ClassConstructor: Instance,\n ...args: any[]\n ): Container> {\n return Container.build().instance(key, ClassConstructor, ...args) as any\n }\n\n register(\n key: TNewKey,\n resolver: (context: TContext, resolvedSet: CountingSet) => TValue,\n { transient }: RegistrationOptions = { transient: false }\n ): Container> {\n if (transient) this._transient.add(key)\n this._resolvers.set(key, resolver)\n return this as any\n }\n\n static register(\n key: TNewKey,\n value: (context: Context) => TValue,\n options?: RegistrationOptions\n ): Container> {\n return Container.build().register(key, value, options) as any\n }\n\n unregister(\n key: TNewKey\n ): Container> {\n this._resolvers.delete(key)\n this._registry.delete(key)\n this._transient.delete(key)\n\n return this as any\n }\n\n static unregister(\n key: TNewKey\n ): Container> {\n return Container.build().unregister(key) as any\n }\n\n resolve(\n key: keyof TContext,\n resolved: CountingSet = new CountingSet()\n ): T {\n if (this._registry.has(key as string)) {\n return this._registry.get(key as string) as unknown as T\n }\n\n const resolver = this._resolvers.get(key as string)!\n if (!resolver) return undefined as unknown as T\n if (resolved.count(resolver) > 1) return undefined as unknown as T\n\n const context = this.context(undefined, resolved.add(resolver))\n\n const value = resolver(\n this._parentContainer?.context(undefined, resolved) ?? context,\n resolved.add(resolver)\n ) as unknown as T\n\n resolved.delete(resolver)\n\n if (!this._transient.has(key as string)) {\n this._registry.set(key as string, value)\n }\n\n return value\n }\n}\n\ntype Serializable =\n | null\n | string\n | number\n | boolean\n | undefined\n | { [key: string]: Serializable }\n | Serializable[]\n\nfunction handleCircularReferences(\n obj: Serializable,\n path: Serializable[] = []\n): Serializable {\n if (obj === null) return null\n if (typeof obj !== \"object\") return obj\n\n const occurrence = path.filter((p) => p === obj).length\n\n // If this object appears more than once in the current path, it's a circular reference.\n if (occurrence > 1) {\n return undefined\n }\n\n // path.push(obj)\n\n let result: Serializable\n if (Array.isArray(obj)) {\n result = obj.map((item) =>\n handleCircularReferences(item, path.slice())\n ) as Serializable[]\n } else {\n result = {}\n for (let key in obj) {\n ;(result as any)[key] = handleCircularReferences(\n (obj as any)[key],\n path.slice()\n )\n }\n }\n\n path.pop()\n\n return result\n}\n", "import { Context } from \"./Container\"\n\nexport class Injected {\n constructor(protected _context: TContext) {}\n}\n"], - "mappings": "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", - "names": [] + "sources": ["../../src/CountingSet.ts", "../../src/Container.ts", "../../src/Singleton.ts"], + "sourcesContent": ["export class CountingSet {\n private readonly _map = new Map()\n private readonly _set = new Set()\n\n add(value: T): this {\n const count = this._map.get(value) || 0\n this._map.set(value, count + 1)\n this._set.add(value)\n return this\n }\n\n delete(value: T): boolean {\n if (this._map.has(value)) {\n const count = this._map.get(value)!\n if (count > 1) {\n this._map.set(value, count - 1)\n } else {\n this._map.delete(value)\n this._set.delete(value)\n }\n return true\n }\n return false\n }\n\n has(value: T): boolean {\n return this._set.has(value)\n }\n\n count(value: T): number {\n return this._map.get(value) || 0\n }\n\n clear(): void {\n this._map.clear()\n this._set.clear()\n }\n\n get size(): number {\n return this._set.size\n }\n\n [Symbol.iterator](): Iterator {\n return this._set[Symbol.iterator]()\n }\n\n forEach(\n callbackfn: (value: T, value2: T, set: Set) => void,\n thisArg?: any\n ): void {\n this._set.forEach(callbackfn, thisArg)\n }\n}\n", "import { CountingSet } from \"./CountingSet\"\n\nexport type Context = {\n [key: string]: unknown\n}\n\nexport type Instance = {\n new (context: any, ...args: any[]): TValue\n}\n\ntype Flatten = {} & { [P in keyof T]: T[P] }\n\ntype AppendContext = Flatten<\n TExisting & {\n [P in TKey]: TValue\n }\n>\n\ntype RegistrationOptions = {\n transient?: boolean\n}\n\nexport class Container {\n private _registry: Map = new Map()\n private _resolvers: Map unknown> = new Map<\n string,\n (context: TContext) => unknown\n >()\n private _cache: Map<(context: TContext) => unknown, unknown> = new Map()\n private _transient: Set = new Set()\n private _base: Record = {}\n\n private _rootContainer: Container | this\n private _parentContainer: Container | this\n\n constructor(rootContainer?: Container, _parentContainer?: Container) {\n this._rootContainer = rootContainer ?? this\n this._parentContainer = _parentContainer ?? this._rootContainer\n }\n\n get root() {\n return this._rootContainer\n }\n\n get parent() {\n return this._parentContainer\n }\n\n static new(): Container {\n return new Container()\n }\n\n static build() {\n return Container.new()\n }\n\n base(base: TBase): Container {\n this._base = base\n return this as any\n }\n\n createContextProxy() {\n const cache: Record = {}\n const resolving = new CountingSet()\n\n const handler = {\n get: (target: TContext, key: string) => {\n if (cache.hasOwnProperty(key)) return cache[key]\n if (target.hasOwnProperty(key)) return target[key]\n\n return resolve(key)\n },\n set: (_target: Context, key: string, value: unknown) => {\n cache[key] = value\n return true\n },\n }\n\n const proxy = new Proxy({}, handler)\n\n const resolve = (key: keyof TContext) => {\n if (this._base?.[key as string])\n return this._base[key as string] as TValue\n\n const resolver = this._resolvers.get(key as string)!\n\n if (this._registry.has(key as string)) {\n resolving.delete(resolver)\n return this._registry.get(key as string) as unknown as TValue\n }\n\n if (resolving.count(resolver) > 1) {\n resolving.delete(resolver)\n return this._cache.get(resolver) as unknown as TValue\n }\n\n const value = resolver?.(\n this._rootContainer.context() as unknown as TContext\n ) as unknown as TValue\n\n resolving.delete(resolver)\n\n if (!this._transient.has(key as string)) {\n this._registry.set(key as string, value)\n this._parentContainer._registry.set(key as string, value)\n this._cache.set(resolver, value)\n }\n\n return value\n }\n\n return proxy\n }\n\n context<\n TWriteContext extends Context = TContext,\n TOverride extends Context = {}\n >(override: TOverride | {} = {}): Flatten {\n // Get all of the keys for the resolvers in this container\n const keys = Array.from(this._resolvers.keys())\n\n const proxy = this.createContextProxy()\n\n const context = keys.reduce(\n (acc, key) => {\n Object.defineProperty(acc, key, {\n get: () => {\n return proxy[key as keyof typeof proxy]\n },\n enumerable: true,\n })\n\n return acc\n },\n { ...this._base } as Flatten\n )\n\n return Object.assign(context, override)\n }\n\n // Add a subcontext to a property of this context\n group(\n key: TNewKey,\n decorator: (container: Container<{}>) => Container\n ) {\n // @ts-expect-error\n // Create a new container for the group and set this container as the parent.\n const groupContainer = decorator(new Container(this._rootContainer, this))\n\n const groupContext = groupContainer.context()\n this.register(key, () => groupContext)\n\n const grouping = Array.from(groupContainer._resolvers.keys()).reduce(\n (acc, key) => {\n return {\n ...acc,\n get [key]() {\n return groupContext[key]\n },\n }\n },\n {} as TNewContext\n )\n\n this._registry.set(key as string, grouping)\n\n return this as Container\n }\n\n singleton(\n key: TNewKey,\n ClassConstructor: Instance,\n ...args: any[]\n ) {\n return this.register(\n key,\n (context) => new ClassConstructor(context, ...args)\n )\n }\n\n instance(\n key: TNewKey,\n ClassConstructor: Instance,\n ...args: any[]\n ) {\n return this.singleton(key, ClassConstructor, ...args)\n }\n\n register(\n key: TNewKey,\n resolver: (context: TContext) => TValue,\n { transient }: RegistrationOptions = { transient: false }\n ): Container> {\n if (transient) this._transient.add(key)\n this._resolvers.set(key, resolver)\n return this as any\n }\n\n unregister(\n key: TNewKey\n ): Container> {\n this._resolvers.delete(key)\n this._registry.delete(key)\n this._transient.delete(key)\n\n return this as any\n }\n\n resolve(key: keyof TContext): TValue {\n const resolver = this._resolvers.get(key as string)\n if (!resolver) throw new Error(`dependency ${String(key)} not registered`)\n return resolver(this._rootContainer.context() as TContext) as TValue\n }\n\n middleware(\n middleware: (container: Container) => Container\n ) {\n return middleware(this)\n }\n}\n", "import { Context } from \"./Container\"\n\ntype PopulatedSingleton = T & { [key in keyof T]: T[key] }\n\n// Mixin to add context-based properties to a class\nexport function WithContextProperties(\n Base: any\n): new (context: T) => PopulatedSingleton {\n return class extends Base {\n constructor(context: T) {\n super(context)\n\n for (const key in context) {\n Object.defineProperty(this, key, {\n get: function () {\n return context[key]\n },\n enumerable: true,\n })\n }\n }\n } as new (context: T) => PopulatedSingleton\n}\n\nexport class Singleton {\n protected _context: TContext\n\n constructor(context: TContext) {\n this._context = context\n }\n}\n"], + "mappings": ";AAAO,IAAM,cAAN,MAAqB;AAAA,EACT,OAAO,oBAAI,IAAe;AAAA,EAC1B,OAAO,oBAAI,IAAO;AAAA,EAEnC,IAAI,OAAgB;AAClB,UAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,KAAK;AACtC,SAAK,KAAK,IAAI,OAAO,QAAQ,CAAC;AAC9B,SAAK,KAAK,IAAI,KAAK;AACnB,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,OAAmB;AACxB,QAAI,KAAK,KAAK,IAAI,KAAK,GAAG;AACxB,YAAM,QAAQ,KAAK,KAAK,IAAI,KAAK;AACjC,UAAI,QAAQ,GAAG;AACb,aAAK,KAAK,IAAI,OAAO,QAAQ,CAAC;AAAA,MAChC,OAAO;AACL,aAAK,KAAK,OAAO,KAAK;AACtB,aAAK,KAAK,OAAO,KAAK;AAAA,MACxB;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,OAAmB;AACrB,WAAO,KAAK,KAAK,IAAI,KAAK;AAAA,EAC5B;AAAA,EAEA,MAAM,OAAkB;AACtB,WAAO,KAAK,KAAK,IAAI,KAAK,KAAK;AAAA,EACjC;AAAA,EAEA,QAAc;AACZ,SAAK,KAAK,MAAM;AAChB,SAAK,KAAK,MAAM;AAAA,EAClB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA,EAEA,CAAC,OAAO,QAAQ,IAAiB;AAC/B,WAAO,KAAK,KAAK,OAAO,QAAQ,EAAE;AAAA,EACpC;AAAA,EAEA,QACE,YACA,SACM;AACN,SAAK,KAAK,QAAQ,YAAY,OAAO;AAAA,EACvC;AACF;;;AC9BO,IAAM,YAAN,MAAM,WAAyC;AAAA,EAC5C,YAAkC,oBAAI,IAAqB;AAAA,EAC3D,aAA0D,oBAAI,IAGpE;AAAA,EACM,SAAuD,oBAAI,IAAI;AAAA,EAC/D,aAA0B,oBAAI,IAAY;AAAA,EAC1C,QAAiC,CAAC;AAAA,EAElC;AAAA,EACA;AAAA,EAER,YAAY,eAA2B,kBAA8B;AACnE,SAAK,iBAAiB,iBAAiB;AACvC,SAAK,mBAAmB,oBAAoB,KAAK;AAAA,EACnD;AAAA,EAEA,IAAI,OAAO;AACT,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,OAAO,MAA4C;AACjD,WAAO,IAAI,WAAa;AAAA,EAC1B;AAAA,EAEA,OAAO,QAAgC;AACrC,WAAO,WAAU,IAAO;AAAA,EAC1B;AAAA,EAEA,KAA4B,MAA0C;AACpE,SAAK,QAAQ;AACb,WAAO;AAAA,EACT;AAAA,EAEA,qBAAqB;AACnB,UAAM,QAAiC,CAAC;AACxC,UAAM,YAAY,IAAI,YAAqB;AAE3C,UAAM,UAAU;AAAA,MACd,KAAK,CAAC,QAAkB,QAAgB;AACtC,YAAI,MAAM,eAAe,GAAG;AAAG,iBAAO,MAAM,GAAG;AAC/C,YAAI,OAAO,eAAe,GAAG;AAAG,iBAAO,OAAO,GAAG;AAEjD,eAAO,QAAQ,GAAG;AAAA,MACpB;AAAA,MACA,KAAK,CAAC,SAAkB,KAAa,UAAmB;AACtD,cAAM,GAAG,IAAI;AACb,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,QAAQ,IAAI,MAAM,CAAC,GAAG,OAAO;AAEnC,UAAM,UAAU,CAAS,QAAwB;AAhFrD;AAiFM,WAAI,UAAK,UAAL,mBAAa;AACf,eAAO,KAAK,MAAM,GAAa;AAEjC,YAAM,WAAW,KAAK,WAAW,IAAI,GAAa;AAElD,UAAI,KAAK,UAAU,IAAI,GAAa,GAAG;AACrC,kBAAU,OAAO,QAAQ;AACzB,eAAO,KAAK,UAAU,IAAI,GAAa;AAAA,MACzC;AAEA,UAAI,UAAU,MAAM,QAAQ,IAAI,GAAG;AACjC,kBAAU,OAAO,QAAQ;AACzB,eAAO,KAAK,OAAO,IAAI,QAAQ;AAAA,MACjC;AAEA,YAAM,QAAQ;AAAA,QACZ,KAAK,eAAe,QAAQ;AAAA;AAG9B,gBAAU,OAAO,QAAQ;AAEzB,UAAI,CAAC,KAAK,WAAW,IAAI,GAAa,GAAG;AACvC,aAAK,UAAU,IAAI,KAAe,KAAK;AACvC,aAAK,iBAAiB,UAAU,IAAI,KAAe,KAAK;AACxD,aAAK,OAAO,IAAI,UAAU,KAAK;AAAA,MACjC;AAEA,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,QAGE,WAA2B,CAAC,GAAuC;AAEnE,UAAM,OAAO,MAAM,KAAK,KAAK,WAAW,KAAK,CAAC;AAE9C,UAAM,QAAQ,KAAK,mBAAmB;AAEtC,UAAM,UAAU,KAAK;AAAA,MACnB,CAAC,KAAK,QAAQ;AACZ,eAAO,eAAe,KAAK,KAAK;AAAA,UAC9B,KAAK,MAAM;AACT,mBAAO,MAAM,GAAyB;AAAA,UACxC;AAAA,UACA,YAAY;AAAA,QACd,CAAC;AAED,eAAO;AAAA,MACT;AAAA,MACA,EAAE,GAAG,KAAK,MAAM;AAAA,IAClB;AAEA,WAAO,OAAO,OAAO,SAAS,QAAQ;AAAA,EACxC;AAAA;AAAA,EAGA,MACE,KACA,WACA;AAGA,UAAM,iBAAiB,UAAU,IAAI,WAAU,KAAK,gBAAgB,IAAI,CAAC;AAEzE,UAAM,eAAe,eAAe,QAAQ;AAC5C,SAAK,SAAS,KAAK,MAAM,YAAY;AAErC,UAAM,WAAW,MAAM,KAAK,eAAe,WAAW,KAAK,CAAC,EAAE;AAAA,MAC5D,CAAC,KAAKA,SAAQ;AACZ,eAAO;AAAA,UACL,GAAG;AAAA,UACH,KAAKA,IAAG,IAAI;AACV,mBAAO,aAAaA,IAAG;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAAA,MACA,CAAC;AAAA,IACH;AAEA,SAAK,UAAU,IAAI,KAAe,QAAQ;AAE1C,WAAO;AAAA,EACT;AAAA,EAEA,UACE,KACA,qBACG,MACH;AACA,WAAO,KAAK;AAAA,MACV;AAAA,MACA,CAAC,YAAY,IAAI,iBAAiB,SAAS,GAAG,IAAI;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,SACE,KACA,qBACG,MACH;AACA,WAAO,KAAK,UAAU,KAAK,kBAAkB,GAAG,IAAI;AAAA,EACtD;AAAA,EAEA,SACE,KACA,UACA,EAAE,UAAU,IAAyB,EAAE,WAAW,MAAM,GACH;AACrD,QAAI;AAAW,WAAK,WAAW,IAAI,GAAG;AACtC,SAAK,WAAW,IAAI,KAAK,QAAQ;AACjC,WAAO;AAAA,EACT;AAAA,EAEA,WACE,KACoC;AACpC,SAAK,WAAW,OAAO,GAAG;AAC1B,SAAK,UAAU,OAAO,GAAG;AACzB,SAAK,WAAW,OAAO,GAAG;AAE1B,WAAO;AAAA,EACT;AAAA,EAEA,QAAgB,KAA6B;AAC3C,UAAM,WAAW,KAAK,WAAW,IAAI,GAAa;AAClD,QAAI,CAAC;AAAU,YAAM,IAAI,MAAM,cAAc,OAAO,GAAG,CAAC,iBAAiB;AACzE,WAAO,SAAS,KAAK,eAAe,QAAQ,CAAa;AAAA,EAC3D;AAAA,EAEA,WACE,YACA;AACA,WAAO,WAAW,IAAI;AAAA,EACxB;AACF;;;ACtNO,SAAS,sBACd,MAC2C;AAC3C,SAAO,cAAc,KAAK;AAAA,IACxB,YAAY,SAAY;AACtB,YAAM,OAAO;AAEb,iBAAW,OAAO,SAAS;AACzB,eAAO,eAAe,MAAM,KAAK;AAAA,UAC/B,KAAK,WAAY;AACf,mBAAO,QAAQ,GAAG;AAAA,UACpB;AAAA,UACA,YAAY;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEO,IAAM,YAAN,MAA0C;AAAA,EACrC;AAAA,EAEV,YAAY,SAAmB;AAC7B,SAAK,WAAW;AAAA,EAClB;AACF;", + "names": ["key"] } diff --git a/packages/nwire/dist/index.d.ts b/packages/nwire/dist/index.d.ts new file mode 100644 index 0000000..8bf4b7e --- /dev/null +++ b/packages/nwire/dist/index.d.ts @@ -0,0 +1,2 @@ +export { Container, Instance, Context } from "./Container"; +export { Singleton, WithContextProperties } from "./Singleton"; diff --git a/packages/nwire/dist/pick.d.ts b/packages/nwire/dist/pick.d.ts new file mode 100644 index 0000000..7246c1c --- /dev/null +++ b/packages/nwire/dist/pick.d.ts @@ -0,0 +1 @@ +export declare function pick(obj: T, ...keys: K[]): Pick; diff --git a/packages/nwire/nodemon.json b/packages/nwire/nodemon.json index f187ea5..892118a 100644 --- a/packages/nwire/nodemon.json +++ b/packages/nwire/nodemon.json @@ -1,6 +1,5 @@ { - "$schema": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/nodemon.json", - "watch": ["./src/*.ts"], - "ignore": ["./src/**/*.test.ts"], + "verbose": true, + "watch": ["src/**/*.ts"], "exec": "pnpm build" } diff --git a/packages/nwire/package.json b/packages/nwire/package.json index 700c9eb..7522abf 100644 --- a/packages/nwire/package.json +++ b/packages/nwire/package.json @@ -1,7 +1,7 @@ { "name": "nwire", "description": "Simplified dependency injection for Node.js", - "version": "1.0.10", + "version": "1.1.0", "repository": { "type": "git", "url": "https://github.com/divmgl/nwire", @@ -24,7 +24,7 @@ "license": "MIT", "scripts": { "build": "rimraf dist && node ./esbuild.config.js && tsc && rimraf dist/*.test.js dist/**/*.test.js", - "dev": "pnpx nodemon", + "dev": "nodemon", "test": "vitest run", "testing": "vitest --inspect --single-thread" }, @@ -32,6 +32,7 @@ "@types/node": "20.8.9", "concurrently": "8.2.2", "esbuild": "0.19.5", + "nodemon": "3.0.1", "rimraf": "5.0.5", "typescript": "5.2.2", "vitest": "0.34.6" diff --git a/packages/nwire/src/Container.test.ts b/packages/nwire/src/Container.test.ts index 2bed55e..670a558 100644 --- a/packages/nwire/src/Container.test.ts +++ b/packages/nwire/src/Container.test.ts @@ -1,96 +1,145 @@ import { describe, expect, it, test } from "vitest" import { Container } from "./Container" -import { Injected } from "./Injected" - -class RandomizerDependency { - constructor() { - this._id = Math.floor(Math.random() * 1000) - } - - private _id: number - get id() { - return this._id - } -} - -type TestContext = { - wheels: Wheels - dependencies: { - machines: { - car: Car - } - } - tire: Transmission -} - -class Wheels { - constructor(_context: TestContext, private n: number) {} - - test() { - return this.n - } -} - -class Car { - constructor(private context: TestContext, private _name: string) {} - - name() { - return this._name - } - - test() { - return this.context.wheels.test() - } -} - -class Transmission { - constructor(private context: TestContext) {} - - belongsTo() { - return this.context.dependencies.machines.car.name() - } -} +import { Singleton, WithContextProperties } from "./Singleton" describe("nwire", () => { it("creates a container", () => { - const container = Container.build() + const container = Container.new() expect(container).toBeTruthy() }) it("registers a dependency", () => { const dependency = { test: () => console.info("testing!") } - Container.register("dependency", () => dependency) + Container.new().register("dependency", () => dependency) }) it("unregisters a dependency", () => { const dependency = { test: () => console.info("testing!") } - const container = Container.build() + const container = Container.new() .register("dependency", () => dependency) .unregister("dependency") - const resolved = container.resolve("dependency" as never) - expect(resolved).toBeUndefined() + expect(() => container.resolve("dependency" as never)).toThrow() + }) + + it("returns undefined when dependency is not found", () => { + const container = Container.new() + + expect(() => + container + .register("hello", () => {}) + .resolve("nonexistentDependency" as never) + ).toThrow() }) - describe("scenario", () => { + it("registers middleware", () => { + const container = Container.new().middleware((container) => { + return container.group("hello", (container) => { + return container.register("world", () => "world") + }) + }) + + const context = container.context() + + expect(context.hello.world).toEqual("world") + }) + + describe("resolution", () => { + it("allows overriding of a registered dependency", () => { + const firstDependency = { test: () => "first" } + const secondDependency = { test: () => "second" } + + const container = Container.new().register( + "dependency", + () => firstDependency + ) + container.register("dependency", () => secondDependency) + + const resolved = container.resolve<{ test: () => {} }>("dependency") + expect(resolved.test()).toBe("second") + }) + + it("resolves deeply nested dependencies", () => { + const container = Container.new() + .singleton("wheels", Wheels, 4) + .group("dependencies", (dependencies) => + dependencies.group("machines", (machines) => + machines.group("deep", (deep) => + deep.register( + "car", + () => new Car(machines.root.context(), "Test Car") + ) + ) + ) + ) + .context() + + expect(container.dependencies.machines.deep.car.test()).toEqual(4) + }) + + it("resolves dependencies in the correct order", () => { + let order = "" + const firstDependency = { test: () => (order += "first") } + const secondDependency = { test: () => (order += "second") } + + const container = Container.new() + .register("first", () => firstDependency) + .register("second", (context) => { + context.first.test() + return secondDependency + }) + + container.resolve("second").test() + + expect(order).toBe("firstsecond") + }) + + it("allows registrations to use future dependencies (fully resolved context)", () => { + const context = Container.new<{ producer: { message: string } }>() + .register("consumer", (context) => context.producer.message) + .register("producer", () => ({ + message: "test", + })) + .context() + + expect(context.consumer).toBe("test") + }) + + it("ensures the correct baseContext is accessed when creating multiple contexts with a single container", () => { + const identifierA = "valueFromContextA" + const identifierB = "valueFromContextB" + + const container = Container.new() + .base({ contextIdentifier: identifierA }) + .base({ contextIdentifier: identifierB }) + .register("contextKey", (context) => context["contextIdentifier"]) + + const contextA = container.context() + const contextB = container.context() + + // The last registered base context should be the one that is used + expect(contextA.contextKey).toBe(identifierB) + expect(contextB.contextKey).toBe(identifierB) + }) + it("resolves registered dependency", () => { const dependency = { test: () => console.info("testing!") } - const container = Container.register("dependency", () => dependency) + const container = Container.new().register("dependency", () => dependency) const resolved = container.resolve("dependency") expect(resolved).toBe(dependency) }) it("resolves classes", () => { - const container = Container.register("wheels", () => Wheels) + const container = Container.new().register("wheels", () => Wheels) const resolved = container.resolve("wheels") expect(resolved).toEqual(Wheels) }) it("creates a context", () => { - const context = Container.build() + const context = Container.new() .register("wheels", () => Wheels) .context() @@ -103,41 +152,45 @@ describe("nwire", () => { }) it("understands singletons", () => { - const context = Container.instance("wheels", Wheels).context() + const context = Container.new().singleton("wheels", Wheels).context() expect(context.wheels.test).toBeTruthy() }) it("creates singletons lazily", () => { - const context = Container.instance("dependent", Car) - .instance("wheels", Wheels, 1) + const context = Container.new() + .singleton("dependent", Car) + .singleton("wheels", Wheels, 1) .context() expect(context.dependent.test()).toEqual(1) }) it("allows groupings of containers", () => { - const context = Container.group("dependencies", (container) => - container.instance("wheels", Wheels) - ).context() + const context = Container.new() + .group("dependencies", (container) => { + return container.singleton("wheels", Wheels) + }) + .context() expect(context.dependencies.wheels.test).toBeTruthy() }) it("groupings have access to lazy dependencies in parent", () => { - const context = Container.instance("wheels", Wheels, 1) - .group("dependencies", (container) => container.instance("car", Car)) + const context = Container.new() + .singleton("wheels", Wheels, 1) + .group("dependencies", (container) => container.singleton("car", Car)) .context() expect(context.dependencies.car.test()).toEqual(1) }) it("groupings have access to lazy dependencies in parent by registration", () => { - const context = Container.build() - .instance("wheels", Wheels, 4) - .group("dependencies", (container) => - container.register( + const context = Container.new() + .singleton("wheels", Wheels, 4) + .group("dependencies", (dependencies) => + dependencies.register( "car", - (container) => new Car(container, "Test Car") + () => new Car(dependencies.root.context(), "Test Car") ) ) .context() @@ -146,100 +199,204 @@ describe("nwire", () => { }) it("dependencies of groupings of groupings resolve correctly", () => { - const context = Container.build() - .instance("wheels", Wheels, 4) - .instance("transmission", Transmission) + const context = Container.new() + .singleton("wheels", Wheels, 4) + .singleton("transmission", Transmission) .group("dependencies", (dependencies) => dependencies.group("machines", (machines) => - machines.register("car", (context) => new Car(context, "Test Car")) + machines.register( + "car", + () => new Car(machines.root.context(), "Test Car") + ) ) ) .context() expect(context.transmission.belongsTo()).toEqual("Test Car") + expect(context.dependencies.machines.car.test()).toEqual(4) }) it("can pass in additional parameters to the constructor of a singleton", () => { - const context = Container.instance("wheels", Wheels, 3) - .group("dependencies", (container) => container.instance("car", Car)) + const context = Container.new() + .singleton("wheels", Wheels, 3) + .group("dependencies", (container) => container.singleton("car", Car)) .context() expect(context.dependencies.car.test()).toEqual(3) }) it("returns a singleton", () => { - const context = Container.instance( - "randomizer", - RandomizerDependency - ).context() + const context = Container.new() + .singleton("randomizer", RandomizerDependency) + .context() expect(context.randomizer.id).toEqual(context.randomizer.id) }) it("does not return a singleton when transient", () => { - const context = Container.register( - "randomizer", - () => new RandomizerDependency(), - { transient: true } - ).context() + const context = Container.new() + .register("randomizer", () => new RandomizerDependency(), { + transient: true, + }) + .context() expect(context.randomizer.id).not.toEqual(context.randomizer.id) }) it("root context takes precedence", () => { - const context = Container.register("hello", () => "hello").context({ - world: "world", - }) + const context = Container.new() + .register("hello", () => "hello") + .context({ world: "world" }) expect(context.world).toEqual("world") }) }) - describe("injected", () => { - it("handles circular references", () => { - type Services = { - a: A - b: B + describe("singletons", () => { + class A extends WithContextProperties(Singleton) { + test() { + return this.services.b.something() } - type TestContext = { - a: A - b: B - services: Services + something() { + return "1" + } + } + class B extends WithContextProperties(Singleton) { + test() { + return this.services.a.something() } - class A extends Injected { - services = this._context.services + something() { + return "2" + } + } - test() { - return this.services.b.something() - } + class C extends WithContextProperties(Singleton) { + test() { + return this.services.a.something() + } - something() { - return "1" - } + something() { + return "7" } - class B extends Injected { - services = this._context.services + } - test() { - return this.services.a.something() - } + class D extends WithContextProperties(Singleton) { + test() { + return this.services.a.something() + } + + something() { + return "7" + } + } - something() { - return "2" - } + class E extends WithContextProperties(Singleton) { + test() { + return this.services.a.something() } + } - const context = Container.instance("a", A) - .instance("b", B) - .register("services", (context) => ({ a: context.a, b: context.b })) - .copy() + type Services = { + a: A + b: B + c: C + d: D + } - console.log(context) + type Events = { + e: E + } - expect(context.b.test()).toEqual("1") - expect(context.a.test()).toEqual("2") + type TestContext = { + services: Services + } + + it("can resolve references that are not yet registered", () => { + const context = Container.new() + .middleware((container) => + container.group("events", (events) => events.singleton("e", E)) + ) + .middleware((container) => + container.group("services", (services) => services.singleton("a", A)) + ) + .context() + + expect(context.events.e.test()).toEqual("1") + }) + + it("can resolve circular references", () => { + const context = Container.new() + .group("services", (container) => + container + .singleton("a", A) + .singleton("b", B) + .singleton("c", C) + .singleton("d", D) + .singleton("e", D) + .singleton("f", A) + ) + .context() + + console.log(context.services) + + expect(context.services).not.toBeUndefined() + expect(context.services.b.test()).toEqual("1") + expect(context.services.a.test()).toEqual("2") + + expect(() => { + expect(context.services.a.services).toEqual(undefined) + }).toThrowError() }) }) }) + +class RandomizerDependency { + constructor() { + this._id = Math.floor(Math.random() * 1000) + } + + private _id: number + get id() { + return this._id + } +} + +type TestContext = { + wheels: Wheels + dependencies: { + machines: { + car: Car + } + } + tire: Transmission +} + +class Wheels { + constructor(_context: TestContext, private n: number) {} + + test() { + return this.n + } +} + +class Car { + constructor(private context: TestContext, private _name: string) {} + + name() { + return this._name + } + + test() { + return this.context.wheels.test() + } +} + +class Transmission { + constructor(private context: TestContext) {} + + belongsTo() { + return this.context.dependencies.machines.car.name() + } +} diff --git a/packages/nwire/src/Container.ts b/packages/nwire/src/Container.ts index e496e1e..4f65241 100644 --- a/packages/nwire/src/Container.ts +++ b/packages/nwire/src/Container.ts @@ -10,7 +10,7 @@ export type Instance = { type Flatten = {} & { [P in keyof T]: T[P] } -type MergeContext = Flatten< +type AppendContext = Flatten< TExisting & { [P in TKey]: TValue } @@ -22,53 +22,53 @@ type RegistrationOptions = { export class Container { private _registry: Map = new Map() - private _resolvers: Map< + private _resolvers: Map unknown> = new Map< string, - (context: TContext, resolved: CountingSet) => unknown - > = new Map unknown>() + (context: TContext) => unknown + >() + private _cache: Map<(context: TContext) => unknown, unknown> = new Map() private _transient: Set = new Set() + private _base: Record = {} - constructor(private _parentContainer?: Container) {} + private _rootContainer: Container | this + private _parentContainer: Container | this - static build(): Container { - return new Container() + constructor(rootContainer?: Container, _parentContainer?: Container) { + this._rootContainer = rootContainer ?? this + this._parentContainer = _parentContainer ?? this._rootContainer } - copy(rootContext: Context = {}): TContext { - // Get all of the keys in the map - const keys = Array.from(this._resolvers.keys()) + get root() { + return this._rootContainer + } - // Create an object that either has the value from the root context, the value from the registry - // or gets the value from the map's generator. - const context = keys.reduce((acc: Record, key) => { - if (rootContext.hasOwnProperty(key)) { - acc[key] = rootContext[key] - } else if (this._registry.has(key)) { - acc[key] = this._registry.get(key) - } else { - acc[key] = this.resolve(key) - } + get parent() { + return this._parentContainer + } - return acc - }, {}) as TContext + static new(): Container { + return new Container() + } - return { - ...context, - ...rootContext, - } as TContext + static build() { + return Container.new() } - context( - rootContext: Context = {}, - resolved: CountingSet = new CountingSet() - ): TWriteContext { + base(base: TBase): Container { + this._base = base + return this as any + } + + createContextProxy() { const cache: Record = {} + const resolving = new CountingSet() const handler = { get: (target: TContext, key: string) => { if (cache.hasOwnProperty(key)) return cache[key] if (target.hasOwnProperty(key)) return target[key] - return this.resolve(key, resolved) + + return resolve(key) }, set: (_target: Context, key: string, value: unknown) => { cache[key] = value @@ -76,64 +76,126 @@ export class Container { }, } - const proxy = new Proxy(rootContext as TContext, handler) + const proxy = new Proxy({}, handler) + + const resolve = (key: keyof TContext) => { + if (this._base?.[key as string]) + return this._base[key as string] as TValue + + const resolver = this._resolvers.get(key as string)! + + if (this._registry.has(key as string)) { + resolving.delete(resolver) + return this._registry.get(key as string) as unknown as TValue + } - return proxy as unknown as TWriteContext + if (resolving.count(resolver) > 1) { + resolving.delete(resolver) + return this._cache.get(resolver) as unknown as TValue + } + + const value = resolver?.( + this._rootContainer.context() as unknown as TContext + ) as unknown as TValue + + resolving.delete(resolver) + + if (!this._transient.has(key as string)) { + this._registry.set(key as string, value) + this._parentContainer._registry.set(key as string, value) + this._cache.set(resolver, value) + } + + return value + } + + return proxy + } + + context< + TWriteContext extends Context = TContext, + TOverride extends Context = {} + >(override: TOverride | {} = {}): Flatten { + // Get all of the keys for the resolvers in this container + const keys = Array.from(this._resolvers.keys()) + + const proxy = this.createContextProxy() + + const context = keys.reduce( + (acc, key) => { + Object.defineProperty(acc, key, { + get: () => { + return proxy[key as keyof typeof proxy] + }, + enumerable: true, + }) + + return acc + }, + { ...this._base } as Flatten + ) + + return Object.assign(context, override) } // Add a subcontext to a property of this context group( key: TNewKey, - decorator: (container: Container) => Container - ): Container> { - const nestedContainer = new Container(this._parentContainer ?? this) - const value = decorator(nestedContainer).context() - this.register(key, () => value) - return this as any - } + decorator: (container: Container<{}>) => Container + ) { + // @ts-expect-error + // Create a new container for the group and set this container as the parent. + const groupContainer = decorator(new Container(this._rootContainer, this)) + + const groupContext = groupContainer.context() + this.register(key, () => groupContext) + + const grouping = Array.from(groupContainer._resolvers.keys()).reduce( + (acc, key) => { + return { + ...acc, + get [key]() { + return groupContext[key] + }, + } + }, + {} as TNewContext + ) - static group( - key: TNewKey, - decorator: (container: Container) => Container - ): Container> { - return Container.build().group(key, decorator) as any + this._registry.set(key as string, grouping) + + return this as Container } - instance( + singleton( key: TNewKey, ClassConstructor: Instance, ...args: any[] - ): Container> { - this.register(key, (context) => new ClassConstructor(context, ...args)) - return this as any + ) { + return this.register( + key, + (context) => new ClassConstructor(context, ...args) + ) } - static instance( + instance( key: TNewKey, ClassConstructor: Instance, ...args: any[] - ): Container> { - return Container.build().instance(key, ClassConstructor, ...args) as any + ) { + return this.singleton(key, ClassConstructor, ...args) } register( key: TNewKey, - resolver: (context: TContext, resolvedSet: CountingSet) => TValue, + resolver: (context: TContext) => TValue, { transient }: RegistrationOptions = { transient: false } - ): Container> { + ): Container> { if (transient) this._transient.add(key) this._resolvers.set(key, resolver) return this as any } - static register( - key: TNewKey, - value: (context: Context) => TValue, - options?: RegistrationOptions - ): Container> { - return Container.build().register(key, value, options) as any - } - unregister( key: TNewKey ): Container> { @@ -144,82 +206,15 @@ export class Container { return this as any } - static unregister( - key: TNewKey - ): Container> { - return Container.build().unregister(key) as any - } - - resolve( - key: keyof TContext, - resolved: CountingSet = new CountingSet() - ): T { - if (this._registry.has(key as string)) { - return this._registry.get(key as string) as unknown as T - } - - const resolver = this._resolvers.get(key as string)! - if (!resolver) return undefined as unknown as T - if (resolved.count(resolver) > 1) return undefined as unknown as T - - const context = this.context(undefined, resolved.add(resolver)) - - const value = resolver( - this._parentContainer?.context(undefined, resolved) ?? context, - resolved.add(resolver) - ) as unknown as T - - resolved.delete(resolver) - - if (!this._transient.has(key as string)) { - this._registry.set(key as string, value) - } - - return value + resolve(key: keyof TContext): TValue { + const resolver = this._resolvers.get(key as string) + if (!resolver) throw new Error(`dependency ${String(key)} not registered`) + return resolver(this._rootContainer.context() as TContext) as TValue } -} -type Serializable = - | null - | string - | number - | boolean - | undefined - | { [key: string]: Serializable } - | Serializable[] - -function handleCircularReferences( - obj: Serializable, - path: Serializable[] = [] -): Serializable { - if (obj === null) return null - if (typeof obj !== "object") return obj - - const occurrence = path.filter((p) => p === obj).length - - // If this object appears more than once in the current path, it's a circular reference. - if (occurrence > 1) { - return undefined - } - - // path.push(obj) - - let result: Serializable - if (Array.isArray(obj)) { - result = obj.map((item) => - handleCircularReferences(item, path.slice()) - ) as Serializable[] - } else { - result = {} - for (let key in obj) { - ;(result as any)[key] = handleCircularReferences( - (obj as any)[key], - path.slice() - ) - } + middleware( + middleware: (container: Container) => Container + ) { + return middleware(this) } - - path.pop() - - return result } diff --git a/packages/nwire/src/Injected.ts b/packages/nwire/src/Injected.ts deleted file mode 100644 index 07ed0d9..0000000 --- a/packages/nwire/src/Injected.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Context } from "./Container" - -export class Injected { - constructor(protected _context: TContext) {} -} diff --git a/packages/nwire/src/Singleton.ts b/packages/nwire/src/Singleton.ts new file mode 100644 index 0000000..cd2b266 --- /dev/null +++ b/packages/nwire/src/Singleton.ts @@ -0,0 +1,31 @@ +import { Context } from "./Container" + +type PopulatedSingleton = T & { [key in keyof T]: T[key] } + +// Mixin to add context-based properties to a class +export function WithContextProperties( + Base: any +): new (context: T) => PopulatedSingleton { + return class extends Base { + constructor(context: T) { + super(context) + + for (const key in context) { + Object.defineProperty(this, key, { + get: function () { + return context[key] + }, + enumerable: true, + }) + } + } + } as new (context: T) => PopulatedSingleton +} + +export class Singleton { + protected _context: TContext + + constructor(context: TContext) { + this._context = context + } +} diff --git a/packages/nwire/src/index.ts b/packages/nwire/src/index.ts index fcde006..cef0885 100644 --- a/packages/nwire/src/index.ts +++ b/packages/nwire/src/index.ts @@ -1,2 +1,2 @@ -export { Container, Instance } from "./Container" -export { Injected } from "./Injected" +export { Container, Instance, Context } from "./Container" +export { Singleton, WithContextProperties } from "./Singleton" diff --git a/packages/nwire/src/pick.ts b/packages/nwire/src/pick.ts new file mode 100644 index 0000000..638f0b1 --- /dev/null +++ b/packages/nwire/src/pick.ts @@ -0,0 +1,12 @@ +export function pick( + obj: T, + ...keys: K[] +): Pick { + const result: Partial = {} + keys.flat().forEach((key) => { + if (key in obj) { + result[key as keyof typeof obj] = obj[key as keyof typeof obj] + } + }) + return result as Pick +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c8822f..78326d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: esbuild: specifier: 0.19.5 version: 0.19.5 + nodemon: + specifier: 3.0.1 + version: 3.0.1 rimraf: specifier: 5.0.5 version: 5.0.5 @@ -646,7 +649,6 @@ packages: /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} requiresBuild: true - dev: false /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -745,6 +747,14 @@ packages: engines: {node: '>=12'} dev: true + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + /aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} dev: false @@ -806,13 +816,17 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} requiresBuild: true dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: false /brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -820,6 +834,13 @@ packages: balanced-match: 1.0.2 dev: true + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: false @@ -899,6 +920,21 @@ packages: get-func-name: 2.0.2 dev: true + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} @@ -950,7 +986,6 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} requiresBuild: true - dev: false /concurrently@8.2.2: resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} @@ -997,6 +1032,18 @@ packages: '@babel/runtime': 7.23.2 dev: true + /debug@3.2.7(supports-color@5.5.0): + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + supports-color: 5.5.0 + dev: true + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -1228,6 +1275,13 @@ packages: reusify: 1.0.4 dev: false + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + /find-my-way@7.7.0: resolution: {integrity: sha512-+SrHpvQ52Q6W9f3wJoJBbAQULJuNEEQwBvlvYwACDhBTLOTMiQ0HYWh4+vC3OivGP2ENcTI1oKlFA2OepJNjhQ==} engines: {node: '>=14'} @@ -1346,6 +1400,13 @@ packages: resolve-pkg-maps: 1.0.0 dev: false + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + /glob@10.3.10: resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} engines: {node: '>=16 || 14 >=14.17'} @@ -1382,6 +1443,11 @@ packages: dev: false optional: true + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1469,6 +1535,10 @@ packages: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false + /ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + dev: true + /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1513,17 +1583,41 @@ packages: engines: {node: '>= 0.10'} dev: false + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} requiresBuild: true + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + /is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} requiresBuild: true dev: false optional: true + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} requiresBuild: true @@ -1648,7 +1742,6 @@ packages: requiresBuild: true dependencies: brace-expansion: 1.1.11 - dev: false /minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} @@ -1746,8 +1839,6 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} requiresBuild: true - dev: false - optional: true /nanoid@3.3.6: resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} @@ -1800,6 +1891,30 @@ packages: dev: false optional: true + /nodemon@3.0.1: + resolution: {integrity: sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + chokidar: 3.5.3 + debug: 3.2.7(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.5.4 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.0 + undefsafe: 2.0.5 + dev: true + + /nopt@1.0.10: + resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: true + /nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} @@ -1808,6 +1923,11 @@ packages: abbrev: 1.1.1 dev: false + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + /npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} dependencies: @@ -1896,6 +2016,11 @@ packages: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + /pino-abstract-transport@1.1.0: resolution: {integrity: sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==} dependencies: @@ -1988,6 +2113,10 @@ packages: ipaddr.js: 1.9.1 dev: false + /pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + dev: true + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -2028,6 +2157,13 @@ packages: string_decoder: 1.3.0 dev: false + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + /real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} @@ -2194,6 +2330,13 @@ packages: engines: {node: '>=14'} dev: true + /simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: true + /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -2365,6 +2508,13 @@ packages: - supports-color dev: true + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2411,11 +2561,25 @@ packages: engines: {node: '>=14.0.0'} dev: true + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + /toad-cache@3.3.0: resolution: {integrity: sha512-3oDzcogWGHZdkwrHyvJVpPjA7oNzY6ENOV3PsWJY9XYPZ6INo94Yd47s5may1U+nleBPwDhrRiTPMIvKaa3MQg==} engines: {node: '>=12'} dev: false + /touch@3.1.0: + resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} + hasBin: true + dependencies: + nopt: 1.0.10 + dev: true + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false @@ -2455,6 +2619,10 @@ packages: resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} dev: true + /undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + dev: true + /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} dev: true