tsdkarc 1.1.4 → 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 } 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,8 +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`
56
+
57
+ // Get the `set` type of the module
58
+ type ConfigModuleSet = ContextWriterOf<typeof configModule>['set'];
59
+ type ConfigModuleSet = SetOf<typeof configModule>
55
60
 
56
61
  // Run
57
62
  (async () => {
package/esm/index.d.ts CHANGED
@@ -116,11 +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 InferContextBy<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
+ /** Get context writter type by typeof module*/
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'];
124
128
  /**
125
129
  * Define a module with full context inferred from its dependency tuple.
126
130
  *
@@ -137,17 +141,92 @@ export type InferContextBy<M> = M extends Module<infer Full, any> ? {
137
141
  *
138
142
  * @param def module definition
139
143
  */
140
- 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> = {
141
167
  name: string;
142
168
  description?: string;
143
169
  modules?: Deps;
144
- 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>>;
145
171
  shutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
146
172
  beforeBoot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
147
173
  afterBoot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
148
174
  beforeShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
149
175
  afterShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
150
- }) => 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>;
151
230
  /**
152
231
  * Errors if U has any key not in T by mapping extra keys to never.
153
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":"ad7ff3da49348197466e1ab9192dab71348ba1cdd4dc9b8651f395b124638399","signature":"f3ba15776feda5eea32b5d3bc2c88a6c52b95121c7bc273f817e6085cb438456"},{"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,11 +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 InferContextBy<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
+ /** Get context writter type by typeof module*/
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'];
124
128
  /**
125
129
  * Define a module with full context inferred from its dependency tuple.
126
130
  *
@@ -137,17 +141,92 @@ export type InferContextBy<M> = M extends Module<infer Full, any> ? {
137
141
  *
138
142
  * @param def module definition
139
143
  */
140
- 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> = {
141
167
  name: string;
142
168
  description?: string;
143
169
  modules?: Deps;
144
- 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>>;
145
171
  shutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
146
172
  beforeBoot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
147
173
  afterBoot?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
148
174
  beforeShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
149
175
  afterShutdown?(ctx: ContextWriter<FullContext<Deps, OwnSlice>, OwnSlice>): Promise<void> | void;
150
- }) => 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>;
151
230
  /**
152
231
  * Errors if U has any key not in T by mapping extra keys to never.
153
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.4",
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",