standard-typed-config 0.0.1

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/HOWTO.md ADDED
@@ -0,0 +1,214 @@
1
+ # How-to Guides
2
+
3
+ Practical tasks for working with `typed-builder`. Each guide assumes you understand the core concepts in [DESIGN.md](./DESIGN.md).
4
+
5
+ ## How to Create a Builder for a New Domain
6
+
7
+ 1. **Define your item configuration type.**
8
+ This is the shape each item in your domain has:
9
+
10
+ ```typescript
11
+ type MyItemConfig = {
12
+ enabled: boolean;
13
+ priority: number;
14
+ };
15
+ ```
16
+
17
+ 2. **Define your hooks map.**
18
+ Each hook declares its `input` (what it receives) and `output` (what it returns). Use `output: void` for terminal hooks that don't accumulate data:
19
+
20
+ ```typescript
21
+ type MyHooks = {
22
+ beforeProcess: { input: { items: Record<string, MyItemConfig> }; output: { cost: number } };
23
+ report: { input: { items: Record<string, MyItemConfig> } & { cost: number }; output: void };
24
+ execute: { input: { items: Record<string, MyItemConfig> } & { cost: number }; output: string };
25
+ };
26
+ ```
27
+
28
+ 3. **Create the builder factory.**
29
+ Pass your hooks map and an array of hook names to `createBuilder`:
30
+
31
+ ```typescript
32
+ const { define } = createBuilder<MyItemConfig, MyHooks>()([
33
+ 'beforeProcess',
34
+ 'report',
35
+ 'execute',
36
+ ]);
37
+ ```
38
+
39
+ 4. **Define items.**
40
+
41
+ ```typescript
42
+ const builder = define({
43
+ itemA: { enabled: true, priority: 1 },
44
+ itemB: { enabled: false, priority: 2 },
45
+ });
46
+ ```
47
+
48
+ 5. **Chain hooks to build your workflow.**
49
+
50
+ ```typescript
51
+ builder
52
+ .beforeProcess((ctx) => ({ cost: 42 }))
53
+ .report((ctx) => {
54
+ // ctx.cost is visible here because beforeProcess accumulated it
55
+ });
56
+ ```
57
+
58
+ ---
59
+
60
+ ## How to Add a New Hook to an Existing Builder
61
+
62
+ 1. **Add a new entry to your `THooksMap`.**
63
+
64
+ ```typescript
65
+ type MyHooks = {
66
+ beforeProcess: { input: { items: Record<string, MyItemConfig> }; output: { cost: number } };
67
+ report: { input: { items: Record<string, MyItemConfig> } & { cost: number }; output: void };
68
+ onRetry: { input: { items: Record<string, MyItemConfig> } & { cost: number }; output: { attempt: number } }; // ← new
69
+ };
70
+ ```
71
+
72
+ 2. **Add the hook name to the array passed to `createBuilder`.**
73
+
74
+ ```typescript
75
+ const { define } = createBuilder<MyItemConfig, MyHooks>()([
76
+ 'beforeProcess',
77
+ 'report',
78
+ 'onRetry', // ← added
79
+ ]);
80
+ ```
81
+
82
+ 3. **Chain the new hook on the builder.**
83
+
84
+ ```typescript
85
+ builder
86
+ .beforeProcess((ctx) => ({ cost: 10 }))
87
+ .onRetry((ctx) => ({ attempt: 1 }))
88
+ .report((ctx) => {
89
+ ctx.cost; // number
90
+ ctx.attempt; // number
91
+ });
92
+ ```
93
+
94
+ The mapped types automatically generate the new method — no changes to the library code needed.
95
+
96
+ ---
97
+
98
+ ## How to Compose Two Builders
99
+
100
+ Use `.use()` to merge two builders when building a larger system from smaller ones.
101
+
102
+ **Prerequisites:** Both builders must share the same `TConfig`.
103
+
104
+ ```typescript
105
+ // Builder A: CI/CD runners
106
+ type RunnerConfig = { capabilities: string[]; region: string };
107
+ type RunnerHooks = {
108
+ beforeJob: { input: { items: Record<string, RunnerConfig> }; output: { estimatedCost: number } };
109
+ afterJob: { input: { items: Record<string, RunnerConfig> } & { estimatedCost: number }; output: void };
110
+ };
111
+ const { define: defineRunners } = createBuilder<RunnerConfig, RunnerHooks>()(['beforeJob', 'afterJob']);
112
+
113
+ // Builder B: notifications (same TConfig)
114
+ type NotifyHooks = {
115
+ notify: { input: { items: Record<string, RunnerConfig> } & { estimatedCost: number }; output: void };
116
+ };
117
+ const { define: defineNotify } = createBuilder<RunnerConfig, NotifyHooks>()(['notify']);
118
+
119
+ // Compose — types merge via intersection
120
+ defineRunners({
121
+ gpu: { capabilities: ['cuda'], region: 'us-east' },
122
+ })
123
+ .beforeJob((ctx) => ({ estimatedCost: 2.5 }))
124
+ .use(defineNotify({
125
+ gpu: { capabilities: ['cuda'], region: 'us-east' }, // same item keys
126
+ }))
127
+ .notify((ctx) => {
128
+ ctx.estimatedCost; // number — from beforeJob, intersected from both builders
129
+ });
130
+ ```
131
+
132
+ **Rule:** Both builders must have the same `TConfig` to compose. Different hook sets are fine — the `THooksMap` types are independent per builder.
133
+
134
+ ---
135
+
136
+ ## How to Use Accumulated Data Across Multiple Hooks
137
+
138
+ Hooks with non-void `output` merge their return values into `TContext`, making data available to all downstream hooks.
139
+
140
+ ```typescript
141
+ type ItemConfig = { base: number };
142
+ type MyHooks = {
143
+ step1: { input: { items: Record<string, ItemConfig> }; output: { value: number } };
144
+ step2: { input: { items: Record<string, ItemConfig> } & { value: number }; output: { total: number } };
145
+ finalize: { input: { items: Record<string, ItemConfig> } & { value: number } & { total: number }; output: void };
146
+ };
147
+
148
+ const { define } = createBuilder<ItemConfig, MyHooks>()(['step1', 'step2', 'finalize']);
149
+
150
+ define({
151
+ item: { base: 10 },
152
+ })
153
+ .step1((ctx) => ({ value: ctx.items.item.base * 2 }))
154
+ .step2((ctx) => ({
155
+ // ctx.value (from step1) is visible here
156
+ total: ctx.value + ctx.items.item.base,
157
+ }))
158
+ .finalize((ctx) => {
159
+ ctx.value; // number — from step1
160
+ ctx.total; // number — from step2
161
+ });
162
+ ```
163
+
164
+ **Rule:** Each hook sees types from hooks declared *before* it in the chain, not after. Order matters.
165
+
166
+ ---
167
+
168
+ ## How to Debug Type Errors in a Builder
169
+
170
+ When TypeScript reports an error on a builder chain, read the type from right to left — each step narrows the types for the next.
171
+
172
+ **Common error: property not in context**
173
+
174
+ ```typescript
175
+ .beforeHook((ctx) => ({ cost: 10 }))
176
+ .finalize((ctx) => {
177
+ ctx.total; // Error: Property 'total' does not exist
178
+ });
179
+ ```
180
+
181
+ **Fix:** `finalize` only sees types accumulated *before* it. Either add a hook that returns `total` before `finalize`, or access it in a different hook.
182
+
183
+ **Common error: key not in items**
184
+
185
+ ```typescript
186
+ builder.items['nonexistent']; // Error: Property 'nonexistent' does not exist
187
+ ```
188
+
189
+ This is correct — the builder only knows about keys declared in `items`. Add the key to `items` or use a key from the routes.
190
+
191
+ **Common error: TConfig mismatch on `.use()`**
192
+
193
+ ```typescript
194
+ type ConfigA = { a: string };
195
+ type ConfigB = { b: number };
196
+ // Both builders must have identical TConfig to compose
197
+ ```
198
+
199
+ The `TConfig` types must match exactly for `.use()` to work. This is intentional — it prevents cross-domain contamination.
200
+
201
+ ---
202
+
203
+ ## How to Mark a Hook as Terminal (Non-accumulating)
204
+
205
+ Use `output: void` to indicate a hook that doesn't pass data downstream:
206
+
207
+ ```typescript
208
+ type MyHooks = {
209
+ accumulating: { input: { items: Record<string, Config> }; output: { cost: number } };
210
+ terminal: { input: { items: Record<string, Config> } & { cost: number }; output: void }; // ← void means no accumulation
211
+ };
212
+ ```
213
+
214
+ Any `output` that is not `Record<string, unknown>` won't merge into `TContext`. The type guard `THooksMap[K]['output'] extends Record<string, unknown> ? THooksMap[K]['output'] : {}` handles this.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # typed-builder
2
+
3
+ A TypeScript library implementing a **builder pattern with accumulating type parameters**. Each method call narrows the types available to subsequent calls, giving you end-to-end type safety without runtime overhead.
4
+
5
+ ```
6
+ npm install typed-builder
7
+ ```
8
+
9
+ ## What Problem It Solves
10
+
11
+ When you chain builder methods, you often lose type information from earlier steps. `typed-builder` solves this by carrying **generic type parameters that narrow with each method call** — the types from early calls are preserved and available downstream.
12
+
13
+ For example, if a `beforeEach` hook returns `{ cost: number }`, that type is visible in every subsequent hook without you re-declaring it.
14
+
15
+ ## Core Concepts
16
+
17
+ | Concept | Role |
18
+ |---------|------|
19
+ | **Accumulating hooks** | Return data that flows downstream to all later hooks |
20
+ | **Terminal hooks** | Consume accumulated data; produce final output |
21
+ | **Composition (`.use()`)** | Merge two builders — their type parameters intersect |
22
+
23
+ See [DESIGN.md](./DESIGN.md) for the full design rationale and type mechanics.
24
+
25
+ ## Quick Start
26
+
27
+ ```typescript
28
+ import { createBuilder } from 'typed-builder';
29
+
30
+ // 1. Define your domain's item shape
31
+ type MenuItemConfig = {
32
+ inStock: boolean;
33
+ price: number;
34
+ };
35
+
36
+ // 2. Create a builder factory for your domain
37
+ const { define } = createBuilder<MenuItemConfig>()({
38
+ accumulating: ['beforePrep'],
39
+ terminal: ['receipt'],
40
+ });
41
+
42
+ // 3. Define items and routes
43
+ define({
44
+ items: {
45
+ burger: { inStock: true, price: 12.99 },
46
+ salad: { inStock: false, price: 8.99 },
47
+ },
48
+ routes: {
49
+ // Routes receive { key, items }
50
+ list: (ctx) => Object.entries(ctx.items).map(([k, v]) => k),
51
+ },
52
+ })
53
+ // 4. Accumulating hook — return data is available downstream
54
+ .beforePrep((key, ctx) => ({ totalPrice: ctx.items[key].price }))
55
+ // 5. Terminal hook — reads accumulated data, produces final output
56
+ .receipt((ctx) => {
57
+ ctx.totalPrice; // number ✓
58
+ return `Total: $${ctx.totalPrice}`;
59
+ });
60
+ ```
61
+
62
+ ## Documentation
63
+
64
+ | Doc | Type | What it covers |
65
+ |-----|------|----------------|
66
+ | [TUTORIAL.md](./TUTORIAL.md) | Tutorial | Build a CI/CD pipeline builder from scratch |
67
+ | [HOWTO.md](./HOWTO.md) | How-to guides | Build a new builder, compose builders, add hooks |
68
+ | [API.md](./API.md) | Reference | `createBuilder`, hook signatures, type parameters |
69
+ | [DESIGN.md](./DESIGN.md) | Explanation | How the type machinery works, why it exists |
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ npm install typed-builder
75
+ # or
76
+ yarn add typed-builder
77
+ # or
78
+ pnpm add typed-builder
79
+ ```
80
+
81
+ Requires TypeScript 5.0+.
82
+
83
+ ## License
84
+
85
+ MIT
package/TUTORIAL.md ADDED
@@ -0,0 +1,152 @@
1
+ # Build a CI/CD Pipeline Builder from Scratch
2
+
3
+ In this tutorial, you will build a typed builder for a CI/CD pipeline domain. By the end, you will have a working builder that tracks estimated cost through job steps and produces a final execution report.
4
+
5
+ No prior experience with `typed-builder` is needed. Familiarity with TypeScript generics is helpful.
6
+
7
+ ## What you're building
8
+
9
+ A pipeline builder for a CI/CD system where:
10
+ - Each **job step** (like `beforeJob`, `afterJob`) can accumulate cost data
11
+ - The `execute` terminal hook produces a final report using all accumulated data
12
+
13
+ ## Step 1 — Define the item configuration type
14
+
15
+ CI/CD runners have a configuration that all jobs share:
16
+
17
+ ```typescript
18
+ type RunnerConfig = {
19
+ capabilities: string[];
20
+ maxParallel: number;
21
+ region: string;
22
+ };
23
+ ```
24
+
25
+ Every runner in your pipeline has this shape.
26
+
27
+ ## Step 2 — Define the hooks map
28
+
29
+ Each hook declares what it **receives** (`input`) and what it **returns** (`output`). Hooks that return `void` are terminal — they don't accumulate data downstream.
30
+
31
+ ```typescript
32
+ type PipelineHooks = {
33
+ // Accumulating: returns cost data that flows to later hooks
34
+ beforeJob: {
35
+ input: { items: Record<string, RunnerConfig> };
36
+ output: { estimatedCost: number };
37
+ };
38
+ // Terminal: receives cost data, produces a final report
39
+ report: {
40
+ input: { items: Record<string, RunnerConfig> } & { estimatedCost: number };
41
+ output: void;
42
+ };
43
+ // Terminal: receives cost data, returns the final result string
44
+ execute: {
45
+ input: { items: Record<string, RunnerConfig> } & { estimatedCost: number };
46
+ output: string;
47
+ };
48
+ };
49
+ ```
50
+
51
+ ## Step 3 — Create the builder factory
52
+
53
+ Pass your config type, hooks map, and an array of hook names:
54
+
55
+ ```typescript
56
+ const { define } = createBuilder<RunnerConfig, PipelineHooks>()([
57
+ 'beforeJob',
58
+ 'report',
59
+ 'execute',
60
+ ]);
61
+ ```
62
+
63
+ ## Step 4 — Define your pipeline runners
64
+
65
+ ```typescript
66
+ const pipeline = define({
67
+ 'gpu-runner': {
68
+ capabilities: ['cuda', 'tensor cores'],
69
+ maxParallel: 4,
70
+ region: 'us-east-1',
71
+ },
72
+ 'cpu-runner': {
73
+ capabilities: ['x86_64'],
74
+ maxParallel: 8,
75
+ region: 'us-west-2',
76
+ },
77
+ });
78
+ ```
79
+
80
+ ## Step 5 — Chain hooks to build the pipeline
81
+
82
+ ```typescript
83
+ const result = pipeline
84
+ // beforeJob accumulates { estimatedCost }
85
+ .beforeJob((ctx) => ({
86
+ estimatedCost: ctx.items['gpu-runner'].maxParallel * 0.25,
87
+ }))
88
+ // execute receives ctx.estimatedCost from beforeJob
89
+ .execute((ctx) => {
90
+ return `Pipeline complete. Estimated cost: $${ctx.estimatedCost}`;
91
+ });
92
+ // result: "Pipeline complete. Estimated cost: $1.00"
93
+ ```
94
+
95
+ ## What just happened
96
+
97
+ | Phase | What changed |
98
+ |-------|-------------|
99
+ | `define({...})` | `TKeys` = `"gpu-runner" \| "cpu-runner"`, `TItems` = the item map, `TContext` = `{}` |
100
+ | `.beforeJob(fn)` | `TContext` narrowed to `{ estimatedCost: number }` |
101
+ | `.execute(fn)` | Receives `{ key: TKeys } & { items: TItems } & { estimatedCost: number }` |
102
+
103
+ TypeScript validates each step. If you try to access `.estimatedCost` inside `beforeJob`, it won't exist yet — that's the point.
104
+
105
+ ## Step 6 — Add a second accumulating hook
106
+
107
+ Suppose you want to track memory usage separately. Add it to the hooks map and re-create the factory:
108
+
109
+ ```typescript
110
+ type PipelineHooks = {
111
+ beforeJob: { input: { items: Record<string, RunnerConfig> }; output: { estimatedCost: number } };
112
+ beforeJobMemory: { input: { items: Record<string, RunnerConfig> } & { estimatedCost: number }; output: { memoryMB: number } };
113
+ report: { input: { items: Record<string, RunnerConfig> } & { estimatedCost: number } & { memoryMB: number }; output: void };
114
+ execute: { input: { items: Record<string, RunnerConfig> } & { estimatedCost: number } & { memoryMB: number }; output: string };
115
+ };
116
+
117
+ const { define } = createBuilder<RunnerConfig, PipelineHooks>()([
118
+ 'beforeJob',
119
+ 'beforeJobMemory',
120
+ 'report',
121
+ 'execute',
122
+ ]);
123
+ ```
124
+
125
+ Now chain both accumulating hooks:
126
+
127
+ ```typescript
128
+ pipeline
129
+ .beforeJob((ctx) => ({ estimatedCost: 1.0 }))
130
+ .beforeJobMemory((ctx) => ({ memoryMB: 512 }))
131
+ .execute((ctx) => {
132
+ ctx.estimatedCost; // number
133
+ ctx.memoryMB; // number — accumulated from beforeJobMemory
134
+ return `Cost: $${ctx.estimatedCost}, Memory: ${ctx.memoryMB}MB`;
135
+ });
136
+ ```
137
+
138
+ Each accumulating hook adds its output to `TContext`. Later hooks see the union of all accumulated data.
139
+
140
+ ## What you learned
141
+
142
+ - How to define a domain with `TConfig` and `THooksMap`
143
+ - How `createBuilder` generates typed methods from hook names
144
+ - How accumulating hooks narrow `TContext` for downstream hooks
145
+ - How terminal hooks with `output: void` consume but don't further accumulate
146
+ - How TypeScript enforces type narrowing at each step in the chain
147
+
148
+ ## Next steps
149
+
150
+ - Read [API.md](./API.md) for the full hook signature reference
151
+ - Read [HOWTO.md](./HOWTO.md) to learn how to compose two builders together
152
+ - Read [DESIGN.md](./DESIGN.md) to understand the type machinery under the hood
@@ -0,0 +1,23 @@
1
+ type THookDef = {
2
+ input: unknown;
3
+ output: unknown;
4
+ };
5
+ type TerminalResult<TConfig, TItems extends Record<string, TConfig>, TContext extends Record<string, unknown>> = {
6
+ config: TConfig;
7
+ items: TItems;
8
+ context: TContext;
9
+ };
10
+ type Builder<TConfig, TKeys extends string, TItems extends Record<string, TConfig>, TContext extends Record<string, unknown>, THooksMap extends Record<string, THookDef>> = {
11
+ use<UKeys extends string, UItems extends Record<string, TConfig>, UContext extends Record<string, unknown>, UHooksMap extends Record<string, THookDef>>(source: Builder<TConfig, UKeys, UItems, UContext, UHooksMap>): Builder<TConfig, TKeys | UKeys, TItems & UItems, TContext & UContext, THooksMap>;
12
+ } & {
13
+ [K in keyof THooksMap]: (fn: (ctx: THooksMap[K]['input'] & {
14
+ key: TKeys;
15
+ } & TItems & TContext) => void | THooksMap[K]['output']) => [THooksMap[K]['output']] extends [never] ? TerminalResult<TConfig, TItems, TContext> : THooksMap[K]['output'] extends Record<string, unknown> ? Builder<TConfig, TKeys, TItems, TContext & THooksMap[K]['output'], THooksMap> : TerminalResult<TConfig, TItems, TContext>;
16
+ };
17
+ declare function createBuilder<TConfig, THooksMap extends Record<string, THookDef>>(hookNames: Array<keyof THooksMap>): {
18
+ define: {
19
+ <TItems extends Record<string, TConfig>>(items: TItems): Builder<TConfig, keyof TItems & string, TItems, {}, THooksMap>;
20
+ (): Builder<TConfig, never, {}, {}, THooksMap>;
21
+ };
22
+ };
23
+ export { createBuilder, type Builder };
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ // ══════════════════════════════════════════════════════════════════════════════════
2
+ // Typed Builder — domain-agnostic builder with accumulating type parameters
3
+ // ══════════════════════════════════════════════════════════════════════════════════
4
+ function createBuilder(hookNames) {
5
+ function define(items) {
6
+ const instance = {};
7
+ instance.use = (source) => instance;
8
+ for (const hookName of hookNames)
9
+ instance[hookName] = (fn) => instance;
10
+ return instance;
11
+ }
12
+ return { define };
13
+ }
14
+ export { createBuilder };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "standard-typed-config",
3
+ "version": "0.0.1",
4
+ "description": "TypeScript pattern: builder with accumulating type parameters, domain-defined hooks, Elysia-style .use() composition",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "typecheck": "tsc --noEmit",
10
+ "test": "tsc --noEmit --strict --skipLibCheck test/types.test-d.ts",
11
+ "prepublishOnly": "npm run build"
12
+ },
13
+ "keywords": [
14
+ "typescript",
15
+ "builder-pattern",
16
+ "type-level",
17
+ "generic",
18
+ "fluent-api",
19
+ "accumulating-types",
20
+ "mapped-types"
21
+ ],
22
+ "license": "MIT",
23
+ "devDependencies": {
24
+ "tsd": "^0.33.0",
25
+ "typescript": "^5.0.0"
26
+ }
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,49 @@
1
+ // ══════════════════════════════════════════════════════════════════════════════════
2
+ // Typed Builder — domain-agnostic builder with accumulating type parameters
3
+ // ══════════════════════════════════════════════════════════════════════════════════
4
+
5
+ type THookDef = { input: unknown; output: unknown };
6
+
7
+ type TerminalResult<TConfig, TItems extends Record<string, TConfig>, TContext extends Record<string, unknown>> = {
8
+ config: TConfig;
9
+ items: TItems;
10
+ context: TContext;
11
+ };
12
+
13
+ type Builder<
14
+ TConfig,
15
+ TKeys extends string,
16
+ TItems extends Record<string, TConfig>,
17
+ TContext extends Record<string, unknown>,
18
+ THooksMap extends Record<string, THookDef>
19
+ > = {
20
+ use<UKeys extends string, UItems extends Record<string, TConfig>, UContext extends Record<string, unknown>, UHooksMap extends Record<string, THookDef>>(
21
+ source: Builder<TConfig, UKeys, UItems, UContext, UHooksMap>
22
+ ): Builder<TConfig, TKeys | UKeys, TItems & UItems, TContext & UContext, THooksMap>;
23
+ } & {
24
+ [K in keyof THooksMap]: (
25
+ fn: (ctx: THooksMap[K]['input'] & { key: TKeys } & TItems & TContext) => void | THooksMap[K]['output']
26
+ ) => [THooksMap[K]['output']] extends [never]
27
+ ? TerminalResult<TConfig, TItems, TContext>
28
+ : THooksMap[K]['output'] extends Record<string, unknown>
29
+ ? Builder<TConfig, TKeys, TItems, TContext & THooksMap[K]['output'], THooksMap>
30
+ : TerminalResult<TConfig, TItems, TContext>;
31
+ };
32
+
33
+ function createBuilder<TConfig, THooksMap extends Record<string, THookDef>>(hookNames: Array<keyof THooksMap>) {
34
+
35
+ function define<TItems extends Record<string, TConfig>>(
36
+ items: TItems
37
+ ): Builder<TConfig, keyof TItems & string, TItems, {}, THooksMap>;
38
+ function define(): Builder<TConfig, never, {}, {}, THooksMap>;
39
+ function define(items?: any): any {
40
+ const instance: any = {};
41
+ instance.use = (source: any) => instance;
42
+ for (const hookName of hookNames) instance[hookName] = (fn: any) => instance;
43
+ return instance;
44
+ }
45
+
46
+ return { define };
47
+ }
48
+
49
+ export { createBuilder, type Builder };