tsdkarc 1.1.5 → 1.2.0

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/README.md CHANGED
@@ -24,13 +24,14 @@ npm install tsdkarc
24
24
  ```
25
25
 
26
26
  ```ts
27
- import start, { defineModule, type InferContextBy, type ContextWriterBy } from "tsdkarc";
27
+ import start, { defineModule, type ContextOf, type ContextWriterOf, type SetOf } from "tsdkarc";
28
28
 
29
- interface ConfigSlice {
30
- config: { port: number; env: string };
31
- }
29
+ // interface ConfigSlice {
30
+ // config: { port: number; env: string };
31
+ // }
32
32
 
33
- const configModule = defineModule<ConfigSlice>()({
33
+ // const configModule = defineModule<ConfigSlice>()({
34
+ const configModule = defineModule()({
34
35
  name: "config",
35
36
  modules: [],
36
37
  boot(ctx) {
@@ -50,10 +51,12 @@ const configModule = defineModule<ConfigSlice>()({
50
51
  },
51
52
  });
52
53
 
53
- // get type from module
54
- type ConfigModuleCtx = InferContextBy<typeof configModule>; // same as `ConfigSlice`
54
+ // Get the module's context type(include the dependencies modules)
55
+ type ConfigModuleCtx = ContextOf<typeof configModule>; // same as `ConfigSlice`
55
56
 
56
- type ConfigModuleSet = ContextWriterBy<typeof configModule>;
57
+ // Get the `set` type of the module
58
+ type ConfigModuleSet = ContextWriterOf<typeof configModule>['set'];
59
+ type ConfigModuleSet = SetOf<typeof configModule>
57
60
 
58
61
  // Run
59
62
  (async () => {
package/esm/index.d.ts CHANGED
@@ -116,14 +116,15 @@ export const moduleA = defineModule<AContext>()({
116
116
  },
117
117
  });
118
118
  // Get context type by module
119
- type AContext2 = InferContextBy<typeof moduleA> // same as above `AContext`
119
+ type AContext2 = ContextOf<typeof moduleA> // same as above `AContext`
120
120
  */
121
- export type ContextBy<M> = M extends Module<infer Full, any> ? {
121
+ export type ContextOf<M> = M extends Module<infer Full, any> ? {
122
122
  [K in keyof Full]: Full[K];
123
123
  } : never;
124
- export type InferContextBy<M> = ContextBy<M>;
125
124
  /** Get context writter type by typeof module*/
126
- export type ContextWriterBy<M> = ContextWriter<M extends Module<any, infer Ctx> ? Ctx : never>;
125
+ 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'];
127
128
  /**
128
129
  * Define a module with full context inferred from its dependency tuple.
129
130
  *
@@ -140,17 +141,92 @@ export type ContextWriterBy<M> = ContextWriter<M extends Module<any, infer Ctx>
140
141
  *
141
142
  * @param def module definition
142
143
  */
143
- export declare function defineModule<OwnSlice extends object = Record<never, never>>(): <const Deps extends readonly AnyModule[] = [], R extends OwnSlice = OwnSlice>(def: {
144
+ /**
145
+ * Extracts the non-void return type of a boot function.
146
+ * Returns Record<never, never> when boot is absent or returns void.
147
+ */
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>;
149
+ /**
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.
152
+ */
153
+ type InferredOwnSlice<Def> = Def extends {
154
+ boot: infer B;
155
+ } ? BootReturn<B> : Record<never, never>;
156
+ /**
157
+ * Infers the Deps tuple from a def object's modules field.
158
+ * Falls back to [] if modules is absent.
159
+ */
160
+ type InferredDeps<Def> = Def extends {
161
+ modules: infer M;
162
+ } ? M extends readonly AnyModule[] ? M : [] : [];
163
+ /**
164
+ * Module def shape when OwnSlice is explicitly provided.
165
+ */
166
+ type ModuleDef<Deps extends readonly AnyModule[], OwnSlice extends object, R extends object> = {
144
167
  name: string;
145
168
  description?: string;
146
169
  modules?: Deps;
147
- boot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, NoOverlap<MergeSlices<Deps>, OwnSlice>>): Promise<void> | void | (R & Exact<NoOverlap<MergeSlices<Deps>, OwnSlice>, R>) | Promise<R & Exact<NoOverlap<MergeSlices<Deps>, OwnSlice>, R>>;
170
+ boot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, NoOverlap<MergeSlices<Deps>, OwnSlice>>): void | Promise<void> | (R & Exact<NoOverlap<MergeSlices<Deps>, OwnSlice>, R>) | Promise<R & Exact<NoOverlap<MergeSlices<Deps>, OwnSlice>, R>>;
148
171
  shutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
149
172
  beforeBoot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
150
173
  afterBoot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
151
174
  beforeShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
152
175
  afterShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
153
- }) => Module<FullContext<Deps, OwnSlice>, OwnSlice>;
176
+ };
177
+ /**
178
+ * Loose module def shape used when OwnSlice is inferred.
179
+ *
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.
182
+ *
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<>.
185
+ */
186
+ type ModuleDefLoose = {
187
+ name: string;
188
+ 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;
196
+ };
197
+ /**
198
+ * Sentinel type used to detect whether OwnSlice was explicitly supplied.
199
+ * When defineModule() is called with no type argument, OwnSlice defaults to
200
+ * this value, triggering the infer-from-boot path.
201
+ */
202
+ declare const INFER: unique symbol;
203
+ type Infer = typeof INFER;
204
+ /**
205
+ * Selects the inner function signature based on whether OwnSlice was supplied.
206
+ *
207
+ * 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>.
210
+ *
211
+ * OwnSlice = explicit type:
212
+ * Enforces boot return against the supplied OwnSlice via Exact/NoOverlap.
213
+ */
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;
215
+ /**
216
+ * Defines a module with optional dependency wiring and lifecycle hooks.
217
+ *
218
+ * Explicit OwnSlice — boot return is enforced against the supplied type:
219
+ * defineModule<{ count: number }>()({ name: "counter", boot: () => ({ count: 0 }) })
220
+ *
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 }>
225
+ *
226
+ * Void / absent boot — OwnSlice becomes Record<never, never>:
227
+ * defineModule()({ name: "logger", boot: () => { console.log("up") } })
228
+ */
229
+ export declare function defineModule<OwnSlice = Infer>(): DefineModuleInner<OwnSlice>;
154
230
  /**
155
231
  * Errors if U has any key not in T by mapping extra keys to never.
156
232
  */
package/esm/index.js CHANGED
@@ -23,24 +23,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
23
23
  step((generator = generator.apply(thisArg, _arguments || [])).next());
24
24
  });
25
25
  };
26
- // ---------------------------------------------------------------------------
27
- // defineModule
28
- // ---------------------------------------------------------------------------
29
26
  /**
30
- * Define a module with full context inferred from its dependency tuple.
31
- *
32
- * OwnSlice first — pass only what this module contributes:
33
- * defineModule<AuthSlice>()({ modules: [db, redis] as const, ... })
27
+ * Defines a module with optional dependency wiring and lifecycle hooks.
34
28
  *
35
- * Deps is inferred from modules no need to pass it explicitly.
36
- * Omit OwnSlice entirely if this module contributes nothing to context:
37
- * defineModule()({ modules: [db] as const, ... })
29
+ * Explicit OwnSlice boot return is enforced against the supplied type:
30
+ * defineModule<{ count: number }>()({ name: "counter", boot: () => ({ count: 0 }) })
38
31
  *
39
- * Inside boot/shutdown:
40
- * ctx.db — read dep directly (Readonly<S>)
41
- * ctx.set('auth') write own slice only
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 }>
42
36
  *
43
- * @param def module definition
37
+ * Void / absent boot — OwnSlice becomes Record<never, never>:
38
+ * defineModule()({ name: "logger", boot: () => { console.log("up") } })
44
39
  */
45
40
  export function defineModule() {
46
41
  return function (def) {
@@ -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":"557b8320eae9f911bada8e74c42d6cedfed77212a6f093b2c005c0883de0bfa6","signature":"04c66a6df6f5d4dec09a5b86cd5dab6623c66f98381b13d50430a47dad9d809a"},{"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":"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"}
package/lib/index.d.ts CHANGED
@@ -116,14 +116,15 @@ export const moduleA = defineModule<AContext>()({
116
116
  },
117
117
  });
118
118
  // Get context type by module
119
- type AContext2 = InferContextBy<typeof moduleA> // same as above `AContext`
119
+ type AContext2 = ContextOf<typeof moduleA> // same as above `AContext`
120
120
  */
121
- export type ContextBy<M> = M extends Module<infer Full, any> ? {
121
+ export type ContextOf<M> = M extends Module<infer Full, any> ? {
122
122
  [K in keyof Full]: Full[K];
123
123
  } : never;
124
- export type InferContextBy<M> = ContextBy<M>;
125
124
  /** Get context writter type by typeof module*/
126
- export type ContextWriterBy<M> = ContextWriter<M extends Module<any, infer Ctx> ? Ctx : never>;
125
+ 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'];
127
128
  /**
128
129
  * Define a module with full context inferred from its dependency tuple.
129
130
  *
@@ -140,17 +141,92 @@ export type ContextWriterBy<M> = ContextWriter<M extends Module<any, infer Ctx>
140
141
  *
141
142
  * @param def module definition
142
143
  */
143
- export declare function defineModule<OwnSlice extends object = Record<never, never>>(): <const Deps extends readonly AnyModule[] = [], R extends OwnSlice = OwnSlice>(def: {
144
+ /**
145
+ * Extracts the non-void return type of a boot function.
146
+ * Returns Record<never, never> when boot is absent or returns void.
147
+ */
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>;
149
+ /**
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.
152
+ */
153
+ type InferredOwnSlice<Def> = Def extends {
154
+ boot: infer B;
155
+ } ? BootReturn<B> : Record<never, never>;
156
+ /**
157
+ * Infers the Deps tuple from a def object's modules field.
158
+ * Falls back to [] if modules is absent.
159
+ */
160
+ type InferredDeps<Def> = Def extends {
161
+ modules: infer M;
162
+ } ? M extends readonly AnyModule[] ? M : [] : [];
163
+ /**
164
+ * Module def shape when OwnSlice is explicitly provided.
165
+ */
166
+ type ModuleDef<Deps extends readonly AnyModule[], OwnSlice extends object, R extends object> = {
144
167
  name: string;
145
168
  description?: string;
146
169
  modules?: Deps;
147
- boot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, NoOverlap<MergeSlices<Deps>, OwnSlice>>): Promise<void> | void | (R & Exact<NoOverlap<MergeSlices<Deps>, OwnSlice>, R>) | Promise<R & Exact<NoOverlap<MergeSlices<Deps>, OwnSlice>, R>>;
170
+ boot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, NoOverlap<MergeSlices<Deps>, OwnSlice>>): void | Promise<void> | (R & Exact<NoOverlap<MergeSlices<Deps>, OwnSlice>, R>) | Promise<R & Exact<NoOverlap<MergeSlices<Deps>, OwnSlice>, R>>;
148
171
  shutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
149
172
  beforeBoot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
150
173
  afterBoot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
151
174
  beforeShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
152
175
  afterShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
153
- }) => Module<FullContext<Deps, OwnSlice>, OwnSlice>;
176
+ };
177
+ /**
178
+ * Loose module def shape used when OwnSlice is inferred.
179
+ *
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.
182
+ *
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<>.
185
+ */
186
+ type ModuleDefLoose = {
187
+ name: string;
188
+ 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;
196
+ };
197
+ /**
198
+ * Sentinel type used to detect whether OwnSlice was explicitly supplied.
199
+ * When defineModule() is called with no type argument, OwnSlice defaults to
200
+ * this value, triggering the infer-from-boot path.
201
+ */
202
+ declare const INFER: unique symbol;
203
+ type Infer = typeof INFER;
204
+ /**
205
+ * Selects the inner function signature based on whether OwnSlice was supplied.
206
+ *
207
+ * 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>.
210
+ *
211
+ * OwnSlice = explicit type:
212
+ * Enforces boot return against the supplied OwnSlice via Exact/NoOverlap.
213
+ */
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;
215
+ /**
216
+ * Defines a module with optional dependency wiring and lifecycle hooks.
217
+ *
218
+ * Explicit OwnSlice — boot return is enforced against the supplied type:
219
+ * defineModule<{ count: number }>()({ name: "counter", boot: () => ({ count: 0 }) })
220
+ *
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 }>
225
+ *
226
+ * Void / absent boot — OwnSlice becomes Record<never, never>:
227
+ * defineModule()({ name: "logger", boot: () => { console.log("up") } })
228
+ */
229
+ export declare function defineModule<OwnSlice = Infer>(): DefineModuleInner<OwnSlice>;
154
230
  /**
155
231
  * Errors if U has any key not in T by mapping extra keys to never.
156
232
  */
package/lib/index.js CHANGED
@@ -30,24 +30,19 @@ exports.default = start;
30
30
  exports.resolveModules = resolveModules;
31
31
  exports.topoSort = topoSort;
32
32
  exports.rollback = rollback;
33
- // ---------------------------------------------------------------------------
34
- // defineModule
35
- // ---------------------------------------------------------------------------
36
33
  /**
37
- * Define a module with full context inferred from its dependency tuple.
38
- *
39
- * OwnSlice first — pass only what this module contributes:
40
- * defineModule<AuthSlice>()({ modules: [db, redis] as const, ... })
34
+ * Defines a module with optional dependency wiring and lifecycle hooks.
41
35
  *
42
- * Deps is inferred from modules no need to pass it explicitly.
43
- * Omit OwnSlice entirely if this module contributes nothing to context:
44
- * defineModule()({ modules: [db] as const, ... })
36
+ * Explicit OwnSlice boot return is enforced against the supplied type:
37
+ * defineModule<{ count: number }>()({ name: "counter", boot: () => ({ count: 0 }) })
45
38
  *
46
- * Inside boot/shutdown:
47
- * ctx.db — read dep directly (Readonly<S>)
48
- * ctx.set('auth') write own slice only
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 }>
49
43
  *
50
- * @param def module definition
44
+ * Void / absent boot — OwnSlice becomes Record<never, never>:
45
+ * defineModule()({ name: "logger", boot: () => { console.log("up") } })
51
46
  */
52
47
  function defineModule() {
53
48
  return function (def) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsdkarc",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
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",