tsdkarc 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Minimal module lifecycle manager.
5
5
  *
6
- * Exports: Module, defineModule, ContextWriter, Logger, LifecycleHooks, StartOptions, start.
6
+ * Exports: Module, defineModule, ContextWriter, LifecycleHooks, StartOptions, start.
7
7
  *
8
8
  * Type helpers (internal):
9
9
  * UnionToIntersection<U> — U1 | U2 | U3 → U1 & U2 & U3
@@ -61,24 +61,14 @@ export interface LifecycleHooks<S extends object = Record<never, never>> {
61
61
  /** Called once after the last module has finished shutting down. */
62
62
  afterShutdown?(ctx: ContextWriter<S>): Promise<void> | void;
63
63
  /** Called before each individual module boots, in boot order. */
64
- beforeEachBoot?(ctx: ContextWriter<S>,
65
- /** The module about to boot. */
66
- module: Module<any>): Promise<void> | void;
64
+ beforeEachBoot?(ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
67
65
  /** Called after each individual module finishes booting, in boot order. */
68
- afterEachBoot?(ctx: ContextWriter<S>,
69
- /** The module that just finished booting. */
70
- module: Module<any>): Promise<void> | void;
66
+ afterEachBoot?(ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
71
67
  /** Called before each individual module shuts down, in shutdown order. */
72
- beforeEachShutdown?(ctx: ContextWriter<S>,
73
- /** The module about to shut down. */
74
- module: Module<any>): Promise<void> | void;
68
+ beforeEachShutdown?(ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
75
69
  /** Called after each individual module finishes shutting down, in shutdown order. */
76
- afterEachShutdown?(ctx: ContextWriter<S>,
77
- /** The module that just finished shutting down. */
78
- module: Module<any>): Promise<void> | void;
79
- onError?(error: Error, ctx: ContextWriter<S>,
80
- /** The module that just finished shutting down. */
81
- module: Module<any>): Promise<void> | void;
70
+ afterEachShutdown?(ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
71
+ onError?(error: Error, ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
82
72
  }
83
73
  /**
84
74
  * Module<S, Sl>
@@ -102,64 +92,38 @@ export interface Module<S extends object, Sl extends object = S> {
102
92
  afterShutdown?(ctx: ContextWriter<S, Sl>): Promise<void> | void;
103
93
  }
104
94
  /**
105
-
106
- type AContext = {
107
- value: string;
108
- };
109
-
110
- export const moduleA = defineModule<AContext>()({
111
- name: "A",
112
- modules: [] as const,
113
- boot: () => ({ value: "module:A" }),
114
- async shutdown(ctx) {
115
- console.log(`shutdown ${ctx.value}`);
116
- },
117
- });
118
- // Get context type by module
119
- type AContext2 = ContextOf<typeof moduleA> // same as above `AContext`
95
+ * Extracts the full context type from a module.
96
+ *
97
+ * @example
98
+ * type AContext = ContextOf<typeof moduleA> // { value: string }
120
99
  */
121
100
  export type ContextOf<M> = M extends Module<infer Full, any> ? {
122
101
  [K in keyof Full]: Full[K];
123
102
  } : never;
124
- /** Get context writter type by typeof module*/
103
+ /** Get ContextWriter type by typeof module. */
125
104
  export type ContextWriterOf<M> = ContextWriter<M extends Module<any, infer Ctx> ? Ctx : never>;
126
- /** Get context writter's `set` type by typeof module*/
127
- export type ContextSetOf<M> = ContextWriter<M extends Module<any, infer Ctx> ? Ctx : never>['set'];
105
+ /** Get the `set` function type from a module's ContextWriter. */
106
+ export type SetOf<M> = ContextWriterOf<M>["set"];
128
107
  /**
129
- * Define a module with full context inferred from its dependency tuple.
130
- *
131
- * OwnSlice first — pass only what this module contributes:
132
- * defineModule<AuthSlice>()({ modules: [db, redis] as const, ... })
133
- *
134
- * Deps is inferred from modules — no need to pass it explicitly.
135
- * Omit OwnSlice entirely if this module contributes nothing to context:
136
- * defineModule()({ modules: [db] as const, ... })
137
- *
138
- * Inside boot/shutdown:
139
- * ctx.db — read dep directly (Readonly<S>)
140
- * ctx.set('auth') — write own slice only
141
- *
142
- * @param def module definition
143
- */
144
- /**
145
- * Extracts the non-void return type of a boot function.
146
- * Returns Record<never, never> when boot is absent or returns void.
108
+ * Errors if U has any key not in T by mapping extra keys to never.
147
109
  */
148
- type BootReturn<T> = T extends (...args: any[]) => Promise<infer R> ? Exclude<R, void> extends never ? Record<never, never> : Exclude<R, void> : T extends (...args: any[]) => infer R ? Exclude<R, void> extends never ? Record<never, never> : Exclude<R, void> : Record<never, never>;
110
+ type Exact<T, U> = {
111
+ [K in keyof U]: K extends keyof T ? T[K] : never;
112
+ };
149
113
  /**
150
- * Infers OwnSlice from a def object's boot return type.
151
- * Falls back to Record<never, never> if no boot or boot returns void.
114
+ * Produces a readable type error message instead of `never`.
115
+ * Msg appears directly in the IDE error output.
152
116
  */
153
- type InferredOwnSlice<Def> = Def extends {
154
- boot: infer B;
155
- } ? BootReturn<B> : Record<never, never>;
117
+ type TypeError<Msg extends string> = {
118
+ readonly __error__: Msg;
119
+ };
156
120
  /**
157
- * Infers the Deps tuple from a def object's modules field.
158
- * Falls back to [] if modules is absent.
121
+ * Errors if OwnSlice declares a key that already exists in DepCtx.
122
+ * Overlapping keys show a readable message instead of `never`.
159
123
  */
160
- type InferredDeps<Def> = Def extends {
161
- modules: infer M;
162
- } ? M extends readonly AnyModule[] ? M : [] : [];
124
+ type NoOverlap<DepCtx, OwnSlice> = {
125
+ [K in keyof OwnSlice]: K extends keyof DepCtx ? TypeError<`Key "${K & string}" is already owned by a dependency module`> : OwnSlice[K];
126
+ };
163
127
  /**
164
128
  * Module def shape when OwnSlice is explicitly provided.
165
129
  */
@@ -175,24 +139,36 @@ type ModuleDef<Deps extends readonly AnyModule[], OwnSlice extends object, R ext
175
139
  afterShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
176
140
  };
177
141
  /**
178
- * Loose module def shape used when OwnSlice is inferred.
142
+ * Module def shape when OwnSlice is inferred from boot's return type.
179
143
  *
180
- * modules is typed as readonly AnyModule[] so TypeScript preserves the literal
181
- * tuple Deps is extracted post-hoc via InferredDeps<Def>, not constrained here.
144
+ * Deps and Slice are independent generics TypeScript infers Deps from the
145
+ * `modules` field and Slice from boot's return value in a single pass, with
146
+ * no mutual dependency between them.
182
147
  *
183
- * boot returns `any` intentionally — Exact<object, R> would collapse every
184
- * property to never if we used `object`. Enforcement is on the output Module<>.
148
+ * Hook ctx types:
149
+ * boot — ContextWriter<MergeSlices<Deps>> (dep context only)
150
+ * boot produces Slice, so Slice cannot appear in its own param
151
+ * type without creating a cycle that collapses ctx to `any`.
152
+ * all other — ContextWriter<FullContext<Deps, Slice>, Slice>
153
+ * these run after boot has written Slice into context, so the
154
+ * full context including own slice is available and safe to type.
185
155
  */
186
- type ModuleDefLoose = {
156
+ type ModuleDefInfer<Deps extends readonly AnyModule[], Slice extends Record<string, unknown>> = {
187
157
  name: string;
188
158
  description?: string;
189
- modules?: readonly AnyModule[];
190
- boot?(ctx: ContextWriter<any, any>): any;
191
- shutdown?(ctx: ContextWriter<any, any>): Promise<void> | void;
192
- beforeBoot?(ctx: ContextWriter<any, any>): Promise<void> | void;
193
- afterBoot?(ctx: ContextWriter<any, any>): Promise<void> | void;
194
- beforeShutdown?(ctx: ContextWriter<any, any>): Promise<void> | void;
195
- afterShutdown?(ctx: ContextWriter<any, any>): Promise<void> | void;
159
+ modules?: Deps;
160
+ /** ctx = dep context only — own slice not available (produced by this function). */
161
+ boot?(ctx: ContextWriter<MergeSlices<Deps>, MergeSlices<Deps>>): void | Promise<void> | Slice | Promise<Slice>;
162
+ /** ctx = full context including own slice — runs after boot. */
163
+ shutdown?(ctx: ContextWriter<FullContext<Deps, Slice>, Slice>): Promise<void> | void;
164
+ /** ctx = dep context only — runs before boot. */
165
+ beforeBoot?(ctx: ContextWriter<MergeSlices<Deps>, MergeSlices<Deps>>): Promise<void> | void;
166
+ /** ctx = full context including own slice — runs after boot. */
167
+ afterBoot?(ctx: ContextWriter<FullContext<Deps, Slice>, Slice>): Promise<void> | void;
168
+ /** ctx = full context including own slice — runs after boot. */
169
+ beforeShutdown?(ctx: ContextWriter<FullContext<Deps, Slice>, Slice>): Promise<void> | void;
170
+ /** ctx = full context including own slice — runs after boot. */
171
+ afterShutdown?(ctx: ContextWriter<FullContext<Deps, Slice>, Slice>): Promise<void> | void;
196
172
  };
197
173
  /**
198
174
  * Sentinel type used to detect whether OwnSlice was explicitly supplied.
@@ -205,48 +181,34 @@ type Infer = typeof INFER;
205
181
  * Selects the inner function signature based on whether OwnSlice was supplied.
206
182
  *
207
183
  * OwnSlice = Infer (no type arg given):
208
- * Captures the whole def as const Def, extracts both Deps and OwnSlice
209
- * from the def object post-hoc via InferredDeps<Def> and InferredOwnSlice<Def>.
184
+ * Deps is inferred from `modules`. Slice is inferred from boot's return.
185
+ * Both are independent generics no cycle, no `any`.
186
+ * boot ctx = dep context only (cycle prevention).
187
+ * all other hook ctx = full context including own slice.
210
188
  *
211
189
  * OwnSlice = explicit type:
212
190
  * Enforces boot return against the supplied OwnSlice via Exact/NoOverlap.
191
+ * Unchanged from original — no regression.
213
192
  */
214
- type DefineModuleInner<OwnSlice> = OwnSlice extends Infer ? <const Def extends ModuleDefLoose>(def: Def) => Module<FullContext<InferredDeps<Def>, Exclude<InferredOwnSlice<Def>, void>>, Exclude<InferredOwnSlice<Def>, void>> : OwnSlice extends object ? <const Deps extends readonly AnyModule[], R extends NoOverlap<MergeSlices<Deps>, OwnSlice>>(def: ModuleDef<Deps, OwnSlice, R>) => Module<FullContext<Deps, OwnSlice>, OwnSlice> : never;
193
+ type DefineModuleInner<OwnSlice> = OwnSlice extends Infer ? <const Deps extends readonly AnyModule[], Slice extends Record<string, unknown> = Record<never, never>>(def: ModuleDefInfer<Deps, Slice>) => Module<FullContext<Deps, Slice>, Slice> : OwnSlice extends object ? <const Deps extends readonly AnyModule[], R extends NoOverlap<MergeSlices<Deps>, OwnSlice>>(def: ModuleDef<Deps, OwnSlice, R>) => Module<FullContext<Deps, OwnSlice>, OwnSlice> : never;
215
194
  /**
216
195
  * Defines a module with optional dependency wiring and lifecycle hooks.
217
196
  *
218
197
  * Explicit OwnSlice — boot return is enforced against the supplied type:
219
198
  * defineModule<{ count: number }>()({ name: "counter", boot: () => ({ count: 0 }) })
220
199
  *
221
- * Inferred OwnSlice — OwnSlice is derived from boot's non-void return type,
222
- * and FullContext includes all dep module contexts via InferredDeps:
223
- * defineModule()({ name: "C", modules: [A, B] as const, boot: () => ({ c: 1 }) })
224
- * // Module<ContextOf<A> & ContextOf<B> & { c: number }, { c: number }>
200
+ * Inferred OwnSlice — Deps inferred from modules, OwnSlice inferred from boot return.
201
+ * ctx types per hook:
202
+ * boot — dep context only (own slice is being produced here, not available yet)
203
+ * shutdown — full context including own slice
204
+ * beforeBoot — dep context only (own slice not yet written)
205
+ * afterBoot — full context including own slice
206
+ * beforeShutdown / afterShutdown — full context including own slice
225
207
  *
226
208
  * Void / absent boot — OwnSlice becomes Record<never, never>:
227
209
  * defineModule()({ name: "logger", boot: () => { console.log("up") } })
228
210
  */
229
211
  export declare function defineModule<OwnSlice = Infer>(): DefineModuleInner<OwnSlice>;
230
- /**
231
- * Errors if U has any key not in T by mapping extra keys to never.
232
- */
233
- type Exact<T, U> = {
234
- [K in keyof U]: K extends keyof T ? T[K] : never;
235
- };
236
- /**
237
- * Produces a readable type error message instead of `never`.
238
- * Msg appears directly in the IDE error output.
239
- */
240
- type TypeError<Msg extends string> = {
241
- readonly __error__: Msg;
242
- };
243
- /**
244
- * Errors if OwnSlice declares a key that already exists in DepCtx.
245
- * Overlapping keys show a readable message instead of `never`.
246
- */
247
- type NoOverlap<DepCtx, OwnSlice> = {
248
- [K in keyof OwnSlice]: K extends keyof DepCtx ? TypeError<`Key "${K & string}" is already owned by a dependency module`> : OwnSlice[K];
249
- };
250
212
  /**
251
213
  * Options for start(). Flat intersection — no nesting required:
252
214
  * start([db], { log, afterBoot: async (ctx) => ... })
package/esm/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Minimal module lifecycle manager.
5
5
  *
6
- * Exports: Module, defineModule, ContextWriter, Logger, LifecycleHooks, StartOptions, start.
6
+ * Exports: Module, defineModule, ContextWriter, LifecycleHooks, StartOptions, start.
7
7
  *
8
8
  * Type helpers (internal):
9
9
  * UnionToIntersection<U> — U1 | U2 | U3 → U1 & U2 & U3
@@ -29,10 +29,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
29
29
  * Explicit OwnSlice — boot return is enforced against the supplied type:
30
30
  * defineModule<{ count: number }>()({ name: "counter", boot: () => ({ count: 0 }) })
31
31
  *
32
- * Inferred OwnSlice — OwnSlice is derived from boot's non-void return type,
33
- * and FullContext includes all dep module contexts via InferredDeps:
34
- * defineModule()({ name: "C", modules: [A, B] as const, boot: () => ({ c: 1 }) })
35
- * // Module<ContextOf<A> & ContextOf<B> & { c: number }, { c: number }>
32
+ * Inferred OwnSlice — Deps inferred from modules, OwnSlice inferred from boot return.
33
+ * ctx types per hook:
34
+ * boot — dep context only (own slice is being produced here, not available yet)
35
+ * shutdown — full context including own slice
36
+ * beforeBoot — dep context only (own slice not yet written)
37
+ * afterBoot — full context including own slice
38
+ * beforeShutdown / afterShutdown — full context including own slice
36
39
  *
37
40
  * Void / absent boot — OwnSlice becomes Record<never, never>:
38
41
  * defineModule()({ name: "logger", boot: () => { console.log("up") } })
@@ -1 +1 @@
1
- {"fileNames":["../node_modules/typescript/lib/lib.es5.d.ts","../node_modules/typescript/lib/lib.es2015.d.ts","../node_modules/typescript/lib/lib.dom.d.ts","../node_modules/typescript/lib/lib.es2015.core.d.ts","../node_modules/typescript/lib/lib.es2015.collection.d.ts","../node_modules/typescript/lib/lib.es2015.generator.d.ts","../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../node_modules/typescript/lib/lib.es2015.promise.d.ts","../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../node_modules/typescript/lib/lib.decorators.d.ts","../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../src/index.ts","../node_modules/@types/deep-eql/index.d.ts","../node_modules/assertion-error/index.d.ts","../node_modules/@types/chai/index.d.ts","../node_modules/@types/estree/index.d.ts"],"fileIdsList":[[16,17]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc1e8f6c60e1772cd804d39523944b7c8e2f5e843cfed2bdc80670e99a459ab4","signature":"fe902df7306c6cdfbb631ee5fd2ae6ddc1d97e5834d91dd6107f07244cc8f45d"},{"version":"427fe2004642504828c1476d0af4270e6ad4db6de78c0b5da3e4c5ca95052a99","impliedFormat":1},{"version":"2eeffcee5c1661ddca53353929558037b8cf305ffb86a803512982f99bcab50d","impliedFormat":99},{"version":"9afb4cb864d297e4092a79ee2871b5d3143ea14153f62ef0bb04ede25f432030","affectsGlobalScope":true,"impliedFormat":99},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1}],"root":[15],"options":{"declaration":true,"downlevelIteration":true,"emitDecoratorMetadata":true,"esModuleInterop":true,"experimentalDecorators":true,"module":5,"noImplicitAny":true,"outDir":"./","skipLibCheck":true,"strict":true,"strictPropertyInitialization":false,"target":2},"referencedMap":[[18,1]],"version":"5.9.3"}
1
+ {"fileNames":["../node_modules/typescript/lib/lib.es5.d.ts","../node_modules/typescript/lib/lib.es2015.d.ts","../node_modules/typescript/lib/lib.dom.d.ts","../node_modules/typescript/lib/lib.es2015.core.d.ts","../node_modules/typescript/lib/lib.es2015.collection.d.ts","../node_modules/typescript/lib/lib.es2015.generator.d.ts","../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../node_modules/typescript/lib/lib.es2015.promise.d.ts","../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../node_modules/typescript/lib/lib.decorators.d.ts","../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../src/index.ts","../node_modules/@types/deep-eql/index.d.ts","../node_modules/assertion-error/index.d.ts","../node_modules/@types/chai/index.d.ts","../node_modules/@types/estree/index.d.ts"],"fileIdsList":[[16,17]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"c855ee070a85ffc84384d7aef265f2b6b6c1ccf6999d69a97940f35cdad9da0b","signature":"671fb5916c01ae3e4bb091627db88d76ce1cebbd493f4855a9d85079774f382a"},{"version":"427fe2004642504828c1476d0af4270e6ad4db6de78c0b5da3e4c5ca95052a99","impliedFormat":1},{"version":"2eeffcee5c1661ddca53353929558037b8cf305ffb86a803512982f99bcab50d","impliedFormat":99},{"version":"9afb4cb864d297e4092a79ee2871b5d3143ea14153f62ef0bb04ede25f432030","affectsGlobalScope":true,"impliedFormat":99},{"version":"151ff381ef9ff8da2da9b9663ebf657eac35c4c9a19183420c05728f31a6761d","impliedFormat":1}],"root":[15],"options":{"declaration":true,"downlevelIteration":true,"emitDecoratorMetadata":true,"esModuleInterop":true,"experimentalDecorators":true,"module":5,"noImplicitAny":true,"outDir":"./","skipLibCheck":true,"strict":true,"strictPropertyInitialization":false,"target":2},"referencedMap":[[18,1]],"version":"5.9.3"}
package/lib/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Minimal module lifecycle manager.
5
5
  *
6
- * Exports: Module, defineModule, ContextWriter, Logger, LifecycleHooks, StartOptions, start.
6
+ * Exports: Module, defineModule, ContextWriter, LifecycleHooks, StartOptions, start.
7
7
  *
8
8
  * Type helpers (internal):
9
9
  * UnionToIntersection<U> — U1 | U2 | U3 → U1 & U2 & U3
@@ -61,24 +61,14 @@ export interface LifecycleHooks<S extends object = Record<never, never>> {
61
61
  /** Called once after the last module has finished shutting down. */
62
62
  afterShutdown?(ctx: ContextWriter<S>): Promise<void> | void;
63
63
  /** Called before each individual module boots, in boot order. */
64
- beforeEachBoot?(ctx: ContextWriter<S>,
65
- /** The module about to boot. */
66
- module: Module<any>): Promise<void> | void;
64
+ beforeEachBoot?(ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
67
65
  /** Called after each individual module finishes booting, in boot order. */
68
- afterEachBoot?(ctx: ContextWriter<S>,
69
- /** The module that just finished booting. */
70
- module: Module<any>): Promise<void> | void;
66
+ afterEachBoot?(ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
71
67
  /** Called before each individual module shuts down, in shutdown order. */
72
- beforeEachShutdown?(ctx: ContextWriter<S>,
73
- /** The module about to shut down. */
74
- module: Module<any>): Promise<void> | void;
68
+ beforeEachShutdown?(ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
75
69
  /** Called after each individual module finishes shutting down, in shutdown order. */
76
- afterEachShutdown?(ctx: ContextWriter<S>,
77
- /** The module that just finished shutting down. */
78
- module: Module<any>): Promise<void> | void;
79
- onError?(error: Error, ctx: ContextWriter<S>,
80
- /** The module that just finished shutting down. */
81
- module: Module<any>): Promise<void> | void;
70
+ afterEachShutdown?(ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
71
+ onError?(error: Error, ctx: ContextWriter<S>, module: Module<any>): Promise<void> | void;
82
72
  }
83
73
  /**
84
74
  * Module<S, Sl>
@@ -102,64 +92,38 @@ export interface Module<S extends object, Sl extends object = S> {
102
92
  afterShutdown?(ctx: ContextWriter<S, Sl>): Promise<void> | void;
103
93
  }
104
94
  /**
105
-
106
- type AContext = {
107
- value: string;
108
- };
109
-
110
- export const moduleA = defineModule<AContext>()({
111
- name: "A",
112
- modules: [] as const,
113
- boot: () => ({ value: "module:A" }),
114
- async shutdown(ctx) {
115
- console.log(`shutdown ${ctx.value}`);
116
- },
117
- });
118
- // Get context type by module
119
- type AContext2 = ContextOf<typeof moduleA> // same as above `AContext`
95
+ * Extracts the full context type from a module.
96
+ *
97
+ * @example
98
+ * type AContext = ContextOf<typeof moduleA> // { value: string }
120
99
  */
121
100
  export type ContextOf<M> = M extends Module<infer Full, any> ? {
122
101
  [K in keyof Full]: Full[K];
123
102
  } : never;
124
- /** Get context writter type by typeof module*/
103
+ /** Get ContextWriter type by typeof module. */
125
104
  export type ContextWriterOf<M> = ContextWriter<M extends Module<any, infer Ctx> ? Ctx : never>;
126
- /** Get context writter's `set` type by typeof module*/
127
- export type ContextSetOf<M> = ContextWriter<M extends Module<any, infer Ctx> ? Ctx : never>['set'];
105
+ /** Get the `set` function type from a module's ContextWriter. */
106
+ export type SetOf<M> = ContextWriterOf<M>["set"];
128
107
  /**
129
- * Define a module with full context inferred from its dependency tuple.
130
- *
131
- * OwnSlice first — pass only what this module contributes:
132
- * defineModule<AuthSlice>()({ modules: [db, redis] as const, ... })
133
- *
134
- * Deps is inferred from modules — no need to pass it explicitly.
135
- * Omit OwnSlice entirely if this module contributes nothing to context:
136
- * defineModule()({ modules: [db] as const, ... })
137
- *
138
- * Inside boot/shutdown:
139
- * ctx.db — read dep directly (Readonly<S>)
140
- * ctx.set('auth') — write own slice only
141
- *
142
- * @param def module definition
143
- */
144
- /**
145
- * Extracts the non-void return type of a boot function.
146
- * Returns Record<never, never> when boot is absent or returns void.
108
+ * Errors if U has any key not in T by mapping extra keys to never.
147
109
  */
148
- type BootReturn<T> = T extends (...args: any[]) => Promise<infer R> ? Exclude<R, void> extends never ? Record<never, never> : Exclude<R, void> : T extends (...args: any[]) => infer R ? Exclude<R, void> extends never ? Record<never, never> : Exclude<R, void> : Record<never, never>;
110
+ type Exact<T, U> = {
111
+ [K in keyof U]: K extends keyof T ? T[K] : never;
112
+ };
149
113
  /**
150
- * Infers OwnSlice from a def object's boot return type.
151
- * Falls back to Record<never, never> if no boot or boot returns void.
114
+ * Produces a readable type error message instead of `never`.
115
+ * Msg appears directly in the IDE error output.
152
116
  */
153
- type InferredOwnSlice<Def> = Def extends {
154
- boot: infer B;
155
- } ? BootReturn<B> : Record<never, never>;
117
+ type TypeError<Msg extends string> = {
118
+ readonly __error__: Msg;
119
+ };
156
120
  /**
157
- * Infers the Deps tuple from a def object's modules field.
158
- * Falls back to [] if modules is absent.
121
+ * Errors if OwnSlice declares a key that already exists in DepCtx.
122
+ * Overlapping keys show a readable message instead of `never`.
159
123
  */
160
- type InferredDeps<Def> = Def extends {
161
- modules: infer M;
162
- } ? M extends readonly AnyModule[] ? M : [] : [];
124
+ type NoOverlap<DepCtx, OwnSlice> = {
125
+ [K in keyof OwnSlice]: K extends keyof DepCtx ? TypeError<`Key "${K & string}" is already owned by a dependency module`> : OwnSlice[K];
126
+ };
163
127
  /**
164
128
  * Module def shape when OwnSlice is explicitly provided.
165
129
  */
@@ -175,24 +139,36 @@ type ModuleDef<Deps extends readonly AnyModule[], OwnSlice extends object, R ext
175
139
  afterShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
176
140
  };
177
141
  /**
178
- * Loose module def shape used when OwnSlice is inferred.
142
+ * Module def shape when OwnSlice is inferred from boot's return type.
179
143
  *
180
- * modules is typed as readonly AnyModule[] so TypeScript preserves the literal
181
- * tuple Deps is extracted post-hoc via InferredDeps<Def>, not constrained here.
144
+ * Deps and Slice are independent generics TypeScript infers Deps from the
145
+ * `modules` field and Slice from boot's return value in a single pass, with
146
+ * no mutual dependency between them.
182
147
  *
183
- * boot returns `any` intentionally — Exact<object, R> would collapse every
184
- * property to never if we used `object`. Enforcement is on the output Module<>.
148
+ * Hook ctx types:
149
+ * boot — ContextWriter<MergeSlices<Deps>> (dep context only)
150
+ * boot produces Slice, so Slice cannot appear in its own param
151
+ * type without creating a cycle that collapses ctx to `any`.
152
+ * all other — ContextWriter<FullContext<Deps, Slice>, Slice>
153
+ * these run after boot has written Slice into context, so the
154
+ * full context including own slice is available and safe to type.
185
155
  */
186
- type ModuleDefLoose = {
156
+ type ModuleDefInfer<Deps extends readonly AnyModule[], Slice extends Record<string, unknown>> = {
187
157
  name: string;
188
158
  description?: string;
189
- modules?: readonly AnyModule[];
190
- boot?(ctx: ContextWriter<any, any>): any;
191
- shutdown?(ctx: ContextWriter<any, any>): Promise<void> | void;
192
- beforeBoot?(ctx: ContextWriter<any, any>): Promise<void> | void;
193
- afterBoot?(ctx: ContextWriter<any, any>): Promise<void> | void;
194
- beforeShutdown?(ctx: ContextWriter<any, any>): Promise<void> | void;
195
- afterShutdown?(ctx: ContextWriter<any, any>): Promise<void> | void;
159
+ modules?: Deps;
160
+ /** ctx = dep context only — own slice not available (produced by this function). */
161
+ boot?(ctx: ContextWriter<MergeSlices<Deps>, MergeSlices<Deps>>): void | Promise<void> | Slice | Promise<Slice>;
162
+ /** ctx = full context including own slice — runs after boot. */
163
+ shutdown?(ctx: ContextWriter<FullContext<Deps, Slice>, Slice>): Promise<void> | void;
164
+ /** ctx = dep context only — runs before boot. */
165
+ beforeBoot?(ctx: ContextWriter<MergeSlices<Deps>, MergeSlices<Deps>>): Promise<void> | void;
166
+ /** ctx = full context including own slice — runs after boot. */
167
+ afterBoot?(ctx: ContextWriter<FullContext<Deps, Slice>, Slice>): Promise<void> | void;
168
+ /** ctx = full context including own slice — runs after boot. */
169
+ beforeShutdown?(ctx: ContextWriter<FullContext<Deps, Slice>, Slice>): Promise<void> | void;
170
+ /** ctx = full context including own slice — runs after boot. */
171
+ afterShutdown?(ctx: ContextWriter<FullContext<Deps, Slice>, Slice>): Promise<void> | void;
196
172
  };
197
173
  /**
198
174
  * Sentinel type used to detect whether OwnSlice was explicitly supplied.
@@ -205,48 +181,34 @@ type Infer = typeof INFER;
205
181
  * Selects the inner function signature based on whether OwnSlice was supplied.
206
182
  *
207
183
  * OwnSlice = Infer (no type arg given):
208
- * Captures the whole def as const Def, extracts both Deps and OwnSlice
209
- * from the def object post-hoc via InferredDeps<Def> and InferredOwnSlice<Def>.
184
+ * Deps is inferred from `modules`. Slice is inferred from boot's return.
185
+ * Both are independent generics no cycle, no `any`.
186
+ * boot ctx = dep context only (cycle prevention).
187
+ * all other hook ctx = full context including own slice.
210
188
  *
211
189
  * OwnSlice = explicit type:
212
190
  * Enforces boot return against the supplied OwnSlice via Exact/NoOverlap.
191
+ * Unchanged from original — no regression.
213
192
  */
214
- type DefineModuleInner<OwnSlice> = OwnSlice extends Infer ? <const Def extends ModuleDefLoose>(def: Def) => Module<FullContext<InferredDeps<Def>, Exclude<InferredOwnSlice<Def>, void>>, Exclude<InferredOwnSlice<Def>, void>> : OwnSlice extends object ? <const Deps extends readonly AnyModule[], R extends NoOverlap<MergeSlices<Deps>, OwnSlice>>(def: ModuleDef<Deps, OwnSlice, R>) => Module<FullContext<Deps, OwnSlice>, OwnSlice> : never;
193
+ type DefineModuleInner<OwnSlice> = OwnSlice extends Infer ? <const Deps extends readonly AnyModule[], Slice extends Record<string, unknown> = Record<never, never>>(def: ModuleDefInfer<Deps, Slice>) => Module<FullContext<Deps, Slice>, Slice> : OwnSlice extends object ? <const Deps extends readonly AnyModule[], R extends NoOverlap<MergeSlices<Deps>, OwnSlice>>(def: ModuleDef<Deps, OwnSlice, R>) => Module<FullContext<Deps, OwnSlice>, OwnSlice> : never;
215
194
  /**
216
195
  * Defines a module with optional dependency wiring and lifecycle hooks.
217
196
  *
218
197
  * Explicit OwnSlice — boot return is enforced against the supplied type:
219
198
  * defineModule<{ count: number }>()({ name: "counter", boot: () => ({ count: 0 }) })
220
199
  *
221
- * Inferred OwnSlice — OwnSlice is derived from boot's non-void return type,
222
- * and FullContext includes all dep module contexts via InferredDeps:
223
- * defineModule()({ name: "C", modules: [A, B] as const, boot: () => ({ c: 1 }) })
224
- * // Module<ContextOf<A> & ContextOf<B> & { c: number }, { c: number }>
200
+ * Inferred OwnSlice — Deps inferred from modules, OwnSlice inferred from boot return.
201
+ * ctx types per hook:
202
+ * boot — dep context only (own slice is being produced here, not available yet)
203
+ * shutdown — full context including own slice
204
+ * beforeBoot — dep context only (own slice not yet written)
205
+ * afterBoot — full context including own slice
206
+ * beforeShutdown / afterShutdown — full context including own slice
225
207
  *
226
208
  * Void / absent boot — OwnSlice becomes Record<never, never>:
227
209
  * defineModule()({ name: "logger", boot: () => { console.log("up") } })
228
210
  */
229
211
  export declare function defineModule<OwnSlice = Infer>(): DefineModuleInner<OwnSlice>;
230
- /**
231
- * Errors if U has any key not in T by mapping extra keys to never.
232
- */
233
- type Exact<T, U> = {
234
- [K in keyof U]: K extends keyof T ? T[K] : never;
235
- };
236
- /**
237
- * Produces a readable type error message instead of `never`.
238
- * Msg appears directly in the IDE error output.
239
- */
240
- type TypeError<Msg extends string> = {
241
- readonly __error__: Msg;
242
- };
243
- /**
244
- * Errors if OwnSlice declares a key that already exists in DepCtx.
245
- * Overlapping keys show a readable message instead of `never`.
246
- */
247
- type NoOverlap<DepCtx, OwnSlice> = {
248
- [K in keyof OwnSlice]: K extends keyof DepCtx ? TypeError<`Key "${K & string}" is already owned by a dependency module`> : OwnSlice[K];
249
- };
250
212
  /**
251
213
  * Options for start(). Flat intersection — no nesting required:
252
214
  * start([db], { log, afterBoot: async (ctx) => ... })
package/lib/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Minimal module lifecycle manager.
6
6
  *
7
- * Exports: Module, defineModule, ContextWriter, Logger, LifecycleHooks, StartOptions, start.
7
+ * Exports: Module, defineModule, ContextWriter, LifecycleHooks, StartOptions, start.
8
8
  *
9
9
  * Type helpers (internal):
10
10
  * UnionToIntersection<U> — U1 | U2 | U3 → U1 & U2 & U3
@@ -36,10 +36,13 @@ exports.rollback = rollback;
36
36
  * Explicit OwnSlice — boot return is enforced against the supplied type:
37
37
  * defineModule<{ count: number }>()({ name: "counter", boot: () => ({ count: 0 }) })
38
38
  *
39
- * Inferred OwnSlice — OwnSlice is derived from boot's non-void return type,
40
- * and FullContext includes all dep module contexts via InferredDeps:
41
- * defineModule()({ name: "C", modules: [A, B] as const, boot: () => ({ c: 1 }) })
42
- * // Module<ContextOf<A> & ContextOf<B> & { c: number }, { c: number }>
39
+ * Inferred OwnSlice — Deps inferred from modules, OwnSlice inferred from boot return.
40
+ * ctx types per hook:
41
+ * boot — dep context only (own slice is being produced here, not available yet)
42
+ * shutdown — full context including own slice
43
+ * beforeBoot — dep context only (own slice not yet written)
44
+ * afterBoot — full context including own slice
45
+ * beforeShutdown / afterShutdown — full context including own slice
43
46
  *
44
47
  * Void / absent boot — OwnSlice becomes Record<never, never>:
45
48
  * defineModule()({ name: "logger", boot: () => { console.log("up") } })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsdkarc",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "TsdkArc is an elegant and fully type-safe module composable library",
5
5
  "repository": "tsdk-monorepo/tsdkarc",
6
6
  "bugs": "https://github.com/tsdk-monorepo/tsdkarc/issues",