@bluelibs/runner 3.2.0 → 3.3.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/README.md +482 -34
- package/dist/cli/extract-docs.d.ts +2 -0
- package/dist/cli/extract-docs.js +88 -0
- package/dist/cli/extract-docs.js.map +1 -0
- package/dist/define.d.ts +21 -1
- package/dist/define.js +95 -23
- package/dist/define.js.map +1 -1
- package/dist/defs/core.d.ts +144 -0
- package/dist/defs/core.js +6 -0
- package/dist/defs/core.js.map +1 -0
- package/dist/defs/symbols.d.ts +42 -0
- package/dist/defs/symbols.js +45 -0
- package/dist/defs/symbols.js.map +1 -0
- package/dist/defs/tags.d.ts +70 -0
- package/dist/defs/tags.js +6 -0
- package/dist/defs/tags.js.map +1 -0
- package/dist/defs.d.ts +168 -16
- package/dist/defs.js +41 -14
- package/dist/defs.js.map +1 -1
- package/dist/docs/introspect.d.ts +7 -0
- package/dist/docs/introspect.js +199 -0
- package/dist/docs/introspect.js.map +1 -0
- package/dist/docs/markdown.d.ts +2 -0
- package/dist/docs/markdown.js +148 -0
- package/dist/docs/markdown.js.map +1 -0
- package/dist/docs/model.d.ts +62 -0
- package/dist/docs/model.js +33 -0
- package/dist/docs/model.js.map +1 -0
- package/dist/express/docsRouter.d.ts +12 -0
- package/dist/express/docsRouter.js +54 -0
- package/dist/express/docsRouter.js.map +1 -0
- package/dist/globals/globalMiddleware.d.ts +1 -0
- package/dist/globals/globalMiddleware.js +2 -0
- package/dist/globals/globalMiddleware.js.map +1 -1
- package/dist/globals/middleware/timeout.middleware.d.ts +8 -0
- package/dist/globals/middleware/timeout.middleware.js +35 -0
- package/dist/globals/middleware/timeout.middleware.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/models/DependencyProcessor.js +2 -2
- package/dist/models/DependencyProcessor.js.map +1 -1
- package/dist/models/Store.d.ts +1 -1
- package/dist/models/StoreConstants.d.ts +1 -1
- package/dist/models/StoreConstants.js +2 -1
- package/dist/models/StoreConstants.js.map +1 -1
- package/dist/models/TaskRunner.d.ts +2 -3
- package/dist/models/TaskRunner.js +1 -2
- package/dist/models/TaskRunner.js.map +1 -1
- package/dist/testing.d.ts +24 -0
- package/dist/testing.js +41 -0
- package/dist/testing.js.map +1 -0
- package/dist/types/dependencies.d.ts +47 -18
- package/dist/types/event.d.ts +49 -0
- package/dist/types/event.js +4 -0
- package/dist/types/event.js.map +1 -0
- package/dist/types/index.d.ts +4 -10
- package/dist/types/index.js +8 -7
- package/dist/types/index.js.map +1 -1
- package/dist/types/metadata.d.ts +75 -0
- package/dist/types/metadata.js +3 -0
- package/dist/types/metadata.js.map +1 -0
- package/dist/types/middleware.d.ts +43 -18
- package/dist/types/middleware.js +0 -3
- package/dist/types/middleware.js.map +1 -1
- package/dist/types/resource.d.ts +96 -0
- package/dist/types/resource.js +3 -0
- package/dist/types/resource.js.map +1 -0
- package/dist/types/symbols.d.ts +17 -0
- package/dist/types/symbols.js +18 -3
- package/dist/types/symbols.js.map +1 -1
- package/dist/types/task.d.ts +68 -0
- package/dist/types/task.js +3 -0
- package/dist/types/task.js.map +1 -0
- package/package.json +4 -4
- package/src/__tests__/benchmark/task-benchmark.test.ts +132 -0
- package/src/__tests__/createTestResource.test.ts +139 -0
- package/src/__tests__/globals/timeout.middleware.test.ts +88 -0
- package/src/__tests__/models/EventManager.test.ts +39 -6
- package/src/__tests__/models/Semaphore.test.ts +1 -1
- package/src/__tests__/override.test.ts +104 -0
- package/src/__tests__/run.overrides.test.ts +50 -21
- package/src/__tests__/run.test.ts +19 -0
- package/src/__tests__/tags.test.ts +396 -0
- package/src/__tests__/tools/getCallerFile.test.ts +9 -11
- package/src/__tests__/typesafety.test.ts +109 -1
- package/src/define.ts +128 -24
- package/src/defs.ts +174 -22
- package/src/globals/globalMiddleware.ts +2 -0
- package/src/globals/middleware/timeout.middleware.ts +46 -0
- package/src/index.ts +6 -0
- package/src/models/DependencyProcessor.ts +2 -10
- package/src/models/StoreConstants.ts +2 -1
- package/src/models/TaskRunner.ts +1 -3
- package/src/testing.ts +66 -0
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
defineTask,
|
|
4
4
|
defineResource,
|
|
5
5
|
defineMiddleware,
|
|
6
|
+
defineOverride,
|
|
7
|
+
defineTag,
|
|
6
8
|
} from "../define";
|
|
7
9
|
import {
|
|
8
10
|
IEventDefinition,
|
|
@@ -12,8 +14,10 @@ import {
|
|
|
12
14
|
ITaskDefinition,
|
|
13
15
|
RegisterableItems,
|
|
14
16
|
} from "../defs";
|
|
17
|
+
import { createTestResource } from "..";
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
// This is skipped because we mostly check typesafety.
|
|
20
|
+
describe.skip("typesafety", () => {
|
|
17
21
|
it("tasks, resources: should have propper type safety for dependeices", async () => {
|
|
18
22
|
type InputTask = {
|
|
19
23
|
message: string;
|
|
@@ -209,4 +213,108 @@ describe("typesafety", () => {
|
|
|
209
213
|
|
|
210
214
|
expect(true).toBe(true);
|
|
211
215
|
});
|
|
216
|
+
|
|
217
|
+
it("createTestResource.runTask: should be type-safe", async () => {
|
|
218
|
+
type Input = { x: number };
|
|
219
|
+
type Output = Promise<number>;
|
|
220
|
+
|
|
221
|
+
const add = defineTask<Input, Output>({
|
|
222
|
+
id: "types.add",
|
|
223
|
+
run: async (i) => i.x + 1,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const depTask = defineTask<{ v: string }, Promise<string>>({
|
|
227
|
+
id: "types.dep",
|
|
228
|
+
run: async (i) => i.v.toUpperCase(),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const main = defineTask<Input, Output, { depTask: typeof depTask }>({
|
|
232
|
+
id: "types.main",
|
|
233
|
+
dependencies: { depTask },
|
|
234
|
+
run: async (i, d) => {
|
|
235
|
+
const v = await d.depTask({ v: String(i.x) });
|
|
236
|
+
return Number(v) + 1;
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const app = defineResource({
|
|
241
|
+
id: "types.app",
|
|
242
|
+
register: [add, depTask, main],
|
|
243
|
+
});
|
|
244
|
+
const harness = createTestResource(app);
|
|
245
|
+
|
|
246
|
+
// Types: input must match, override deps must match, output is awaited number
|
|
247
|
+
const { value: t } = await (await import("../run")).run(harness);
|
|
248
|
+
const r1: number | undefined = await t.runTask(add, { x: 1 });
|
|
249
|
+
// @ts-expect-error wrong input type
|
|
250
|
+
await t.runTask(add, { z: 1 });
|
|
251
|
+
// @ts-expect-error missing input
|
|
252
|
+
await t.runTask(add);
|
|
253
|
+
|
|
254
|
+
const r2: number | undefined = await t.runTask(main, { x: 2 });
|
|
255
|
+
|
|
256
|
+
// @ts-expect-error wrong deps override type
|
|
257
|
+
await t.runTask(main, { x: 2 }, { depTask: async (i: number) => "x" });
|
|
258
|
+
|
|
259
|
+
expect(true).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should have propper type safety for overrides", async () => {
|
|
263
|
+
const task = defineTask({
|
|
264
|
+
id: "task",
|
|
265
|
+
run: async () => "Task executed",
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// @ts-expect-error
|
|
269
|
+
const overrideTask = defineOverride(task, {
|
|
270
|
+
run: async () => 234,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const resource = defineResource({
|
|
274
|
+
id: "resource",
|
|
275
|
+
register: [task],
|
|
276
|
+
init: async () => "Resource executed",
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const overrideResource = defineOverride(resource, {
|
|
280
|
+
init: async () => "Resource overridden",
|
|
281
|
+
});
|
|
282
|
+
// @ts-expect-error
|
|
283
|
+
defineOverride(resource, {
|
|
284
|
+
init: async () => 123, // bad type
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const middleware = defineMiddleware({
|
|
288
|
+
id: "middleware",
|
|
289
|
+
run: async () => "Middleware executed",
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
expect(true).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should have propper type safety for tags", async () => {
|
|
296
|
+
const tag = defineTag({ id: "tag" });
|
|
297
|
+
const tag2 = defineTag<{ value: number }>({ id: "tag2" });
|
|
298
|
+
const tag2optional = defineTag<{ value?: number }>({ id: "tag2" });
|
|
299
|
+
|
|
300
|
+
const tag3 = tag2.with({ value: 123 });
|
|
301
|
+
// @ts-expect-error
|
|
302
|
+
const tag4 = tag.with({ value: 123 });
|
|
303
|
+
|
|
304
|
+
const task = defineTask({
|
|
305
|
+
id: "task",
|
|
306
|
+
meta: {
|
|
307
|
+
tags: [
|
|
308
|
+
tag,
|
|
309
|
+
// @ts-expect-error
|
|
310
|
+
tag2,
|
|
311
|
+
tag2optional,
|
|
312
|
+
tag2.with({ value: 123 }),
|
|
313
|
+
tag3,
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
expect(true).toBe(true);
|
|
319
|
+
});
|
|
212
320
|
});
|
package/src/define.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory functions for defining tasks, resources, events and middleware.
|
|
3
|
+
*
|
|
4
|
+
* These helpers create strongly-typed definitions while also wiring internal
|
|
5
|
+
* metadata: anonymous IDs, file path tags (for better debugging), lifecycle
|
|
6
|
+
* events, and global middleware flags. See README for high-level concepts.
|
|
7
|
+
*/
|
|
1
8
|
import {
|
|
2
9
|
ITask,
|
|
3
10
|
ITaskDefinition,
|
|
@@ -6,7 +13,6 @@ import {
|
|
|
6
13
|
IResourceDefinition,
|
|
7
14
|
IEventDefinition,
|
|
8
15
|
IMiddlewareDefinition,
|
|
9
|
-
symbols,
|
|
10
16
|
DependencyMapType,
|
|
11
17
|
DependencyValuesType,
|
|
12
18
|
IMiddleware,
|
|
@@ -16,6 +22,17 @@ import {
|
|
|
16
22
|
symbolMiddlewareConfigured,
|
|
17
23
|
symbolFilePath,
|
|
18
24
|
symbolIndexResource,
|
|
25
|
+
ITag,
|
|
26
|
+
ITagDefinition,
|
|
27
|
+
ITagWithConfig,
|
|
28
|
+
TagType,
|
|
29
|
+
ITaggable,
|
|
30
|
+
symbolTask,
|
|
31
|
+
symbolMiddlewareEverywhereTasks,
|
|
32
|
+
symbolMiddlewareEverywhereResources,
|
|
33
|
+
symbolResourceWithConfig,
|
|
34
|
+
symbolResource,
|
|
35
|
+
symbolMiddleware,
|
|
19
36
|
} from "./defs";
|
|
20
37
|
import { Errors } from "./errors";
|
|
21
38
|
import { generateCallerIdFromFile, getCallerFile } from "./tools/getCallerFile";
|
|
@@ -30,12 +47,18 @@ export function defineTask<
|
|
|
30
47
|
>(
|
|
31
48
|
taskConfig: ITaskDefinition<Input, Output, Deps, TOn>
|
|
32
49
|
): ITask<Input, Output, Deps, TOn> {
|
|
50
|
+
/**
|
|
51
|
+
* Creates a task definition.
|
|
52
|
+
* - Generates an anonymous id based on file path when `id` is omitted
|
|
53
|
+
* - Wires lifecycle events: beforeRun, afterRun, onError
|
|
54
|
+
* - Carries through dependencies and middleware as declared
|
|
55
|
+
*/
|
|
33
56
|
const filePath = getCallerFile();
|
|
34
57
|
const isAnonymous = !Boolean(taskConfig.id);
|
|
35
58
|
const id = taskConfig.id || generateCallerIdFromFile(filePath, "task");
|
|
36
59
|
return {
|
|
37
|
-
[
|
|
38
|
-
[
|
|
60
|
+
[symbolTask]: true,
|
|
61
|
+
[symbolFilePath]: filePath,
|
|
39
62
|
id,
|
|
40
63
|
dependencies: taskConfig.dependencies || ({} as Deps),
|
|
41
64
|
middleware: taskConfig.middleware || [],
|
|
@@ -49,7 +72,7 @@ export function defineTask<
|
|
|
49
72
|
? Symbol(`anonymous-task.events.beforeRun`)
|
|
50
73
|
: `${id as string}.events.beforeRun`,
|
|
51
74
|
}),
|
|
52
|
-
[
|
|
75
|
+
[symbolFilePath]: getCallerFile(),
|
|
53
76
|
},
|
|
54
77
|
afterRun: {
|
|
55
78
|
...defineEvent({
|
|
@@ -57,7 +80,7 @@ export function defineTask<
|
|
|
57
80
|
? Symbol(`anonymous-task.events.afterRun`)
|
|
58
81
|
: `${id as string}.events.afterRun`,
|
|
59
82
|
}),
|
|
60
|
-
[
|
|
83
|
+
[symbolFilePath]: getCallerFile(),
|
|
61
84
|
},
|
|
62
85
|
onError: {
|
|
63
86
|
...defineEvent({
|
|
@@ -65,7 +88,7 @@ export function defineTask<
|
|
|
65
88
|
? Symbol(`anonymous-task.events.onError`)
|
|
66
89
|
: `${id as string}.events.onError`,
|
|
67
90
|
}),
|
|
68
|
-
[
|
|
91
|
+
[symbolFilePath]: getCallerFile(),
|
|
69
92
|
},
|
|
70
93
|
},
|
|
71
94
|
meta: taskConfig.meta || {},
|
|
@@ -81,6 +104,12 @@ export function defineResource<
|
|
|
81
104
|
>(
|
|
82
105
|
constConfig: IResourceDefinition<TConfig, TValue, TDeps, TPrivate>
|
|
83
106
|
): IResource<TConfig, TValue, TDeps, TPrivate> {
|
|
107
|
+
/**
|
|
108
|
+
* Creates a resource definition.
|
|
109
|
+
* - Generates anonymous id when omitted (resource or index flavor)
|
|
110
|
+
* - Wires lifecycle events: beforeInit, afterInit, onError
|
|
111
|
+
* - Exposes `.with(config)` for config‑bound registration
|
|
112
|
+
*/
|
|
84
113
|
// The symbolFilePath might already come from defineIndex() for example
|
|
85
114
|
const filePath: string = constConfig[symbolFilePath] || getCallerFile();
|
|
86
115
|
const isIndexResource = constConfig[symbolIndexResource] || false;
|
|
@@ -88,9 +117,11 @@ export function defineResource<
|
|
|
88
117
|
const id =
|
|
89
118
|
constConfig.id ||
|
|
90
119
|
generateCallerIdFromFile(filePath, isIndexResource ? "index" : "resource");
|
|
120
|
+
|
|
91
121
|
return {
|
|
92
|
-
[
|
|
93
|
-
[
|
|
122
|
+
[symbolResource]: true,
|
|
123
|
+
[symbolFilePath]: filePath,
|
|
124
|
+
[symbolIndexResource]: isIndexResource,
|
|
94
125
|
id,
|
|
95
126
|
dependencies: constConfig.dependencies,
|
|
96
127
|
dispose: constConfig.dispose,
|
|
@@ -100,7 +131,7 @@ export function defineResource<
|
|
|
100
131
|
context: constConfig.context,
|
|
101
132
|
with: function (config: TConfig) {
|
|
102
133
|
return {
|
|
103
|
-
[
|
|
134
|
+
[symbolResourceWithConfig]: true,
|
|
104
135
|
id: this.id,
|
|
105
136
|
resource: this,
|
|
106
137
|
config,
|
|
@@ -114,7 +145,7 @@ export function defineResource<
|
|
|
114
145
|
? Symbol(`anonymous-resource.events.beforeInit`)
|
|
115
146
|
: `${id as string}.events.beforeInit`,
|
|
116
147
|
}),
|
|
117
|
-
[
|
|
148
|
+
[symbolFilePath]: filePath,
|
|
118
149
|
},
|
|
119
150
|
afterInit: {
|
|
120
151
|
...defineEvent({
|
|
@@ -122,7 +153,7 @@ export function defineResource<
|
|
|
122
153
|
? Symbol(`anonymous-resource.events.afterInit`)
|
|
123
154
|
: `${id as string}.events.afterInit`,
|
|
124
155
|
}),
|
|
125
|
-
[
|
|
156
|
+
[symbolFilePath]: filePath,
|
|
126
157
|
},
|
|
127
158
|
onError: {
|
|
128
159
|
...defineEvent({
|
|
@@ -130,7 +161,7 @@ export function defineResource<
|
|
|
130
161
|
? Symbol(`anonymous-resource.events.onError`)
|
|
131
162
|
: `${id as string}.events.onError`,
|
|
132
163
|
}),
|
|
133
|
-
[
|
|
164
|
+
[symbolFilePath]: filePath,
|
|
134
165
|
},
|
|
135
166
|
},
|
|
136
167
|
meta: constConfig.meta || {},
|
|
@@ -152,6 +183,7 @@ export function defineIndex<
|
|
|
152
183
|
: T[K];
|
|
153
184
|
} & DependencyMapType
|
|
154
185
|
>(items: T): IResource<void, DependencyValuesType<D>, D> {
|
|
186
|
+
// Build dependency map from given items; unwrap `.with()` to the base resource
|
|
155
187
|
const dependencies = {} as D;
|
|
156
188
|
const register: RegisterableItems[] = [];
|
|
157
189
|
|
|
@@ -173,20 +205,24 @@ export function defineIndex<
|
|
|
173
205
|
async init(_, deps) {
|
|
174
206
|
return deps as any;
|
|
175
207
|
},
|
|
176
|
-
[
|
|
177
|
-
[
|
|
208
|
+
[symbolFilePath]: callerFilePath,
|
|
209
|
+
[symbolIndexResource]: true,
|
|
178
210
|
});
|
|
179
211
|
}
|
|
180
212
|
|
|
181
213
|
export function defineEvent<TPayload = void>(
|
|
182
214
|
config?: IEventDefinition<TPayload>
|
|
183
215
|
): IEvent<TPayload> {
|
|
216
|
+
/**
|
|
217
|
+
* Creates an event definition. Anonymous ids are generated from file path
|
|
218
|
+
* when omitted. The returned object is branded for runtime checks.
|
|
219
|
+
*/
|
|
184
220
|
const callerFilePath = getCallerFile();
|
|
185
221
|
const eventConfig = config || {};
|
|
186
222
|
return {
|
|
187
223
|
...eventConfig,
|
|
188
224
|
id: eventConfig.id || generateCallerIdFromFile(callerFilePath, "event"),
|
|
189
|
-
[
|
|
225
|
+
[symbolFilePath]: callerFilePath,
|
|
190
226
|
[symbolEvent]: true, // This is a workaround
|
|
191
227
|
};
|
|
192
228
|
}
|
|
@@ -208,10 +244,16 @@ export function defineMiddleware<
|
|
|
208
244
|
>(
|
|
209
245
|
middlewareDef: IMiddlewareDefinition<TConfig, TDependencies>
|
|
210
246
|
): IMiddleware<TConfig, TDependencies> {
|
|
247
|
+
/**
|
|
248
|
+
* Creates a middleware definition with:
|
|
249
|
+
* - Anonymous id generation when omitted
|
|
250
|
+
* - `.with(config)` to create configured instances
|
|
251
|
+
* - `.everywhere()` to mark as global (optionally scoping to tasks/resources)
|
|
252
|
+
*/
|
|
211
253
|
const filePath = getCallerFile();
|
|
212
254
|
const object = {
|
|
213
|
-
[
|
|
214
|
-
[
|
|
255
|
+
[symbolFilePath]: filePath,
|
|
256
|
+
[symbolMiddleware]: true,
|
|
215
257
|
config: {} as TConfig,
|
|
216
258
|
id: middlewareDef.id || generateCallerIdFromFile(filePath, "middleware"),
|
|
217
259
|
...middlewareDef,
|
|
@@ -235,8 +277,8 @@ export function defineMiddleware<
|
|
|
235
277
|
|
|
236
278
|
return {
|
|
237
279
|
...object,
|
|
238
|
-
[
|
|
239
|
-
[
|
|
280
|
+
[symbolMiddlewareEverywhereTasks]: tasks,
|
|
281
|
+
[symbolMiddlewareEverywhereResources]: resources,
|
|
240
282
|
everywhere() {
|
|
241
283
|
throw Errors.middlewareAlreadyGlobal(object.id);
|
|
242
284
|
},
|
|
@@ -246,23 +288,85 @@ export function defineMiddleware<
|
|
|
246
288
|
}
|
|
247
289
|
|
|
248
290
|
export function isTask(definition: any): definition is ITask {
|
|
249
|
-
return definition && definition[
|
|
291
|
+
return definition && definition[symbolTask];
|
|
250
292
|
}
|
|
251
293
|
|
|
252
294
|
export function isResource(definition: any): definition is IResource {
|
|
253
|
-
return definition && definition[
|
|
295
|
+
return definition && definition[symbolResource];
|
|
254
296
|
}
|
|
255
297
|
|
|
256
298
|
export function isResourceWithConfig(
|
|
257
299
|
definition: any
|
|
258
300
|
): definition is IResourceWithConfig {
|
|
259
|
-
return definition && definition[
|
|
301
|
+
return definition && definition[symbolResourceWithConfig];
|
|
260
302
|
}
|
|
261
303
|
|
|
262
304
|
export function isEvent(definition: any): definition is IEvent {
|
|
263
|
-
return definition && definition[
|
|
305
|
+
return definition && definition[symbolEvent];
|
|
264
306
|
}
|
|
265
307
|
|
|
266
308
|
export function isMiddleware(definition: any): definition is IMiddleware {
|
|
267
|
-
return definition && definition[
|
|
309
|
+
return definition && definition[symbolMiddleware];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Override helper that preserves the original `id` and returns the same type.
|
|
314
|
+
* You can override any property except `id`.
|
|
315
|
+
*/
|
|
316
|
+
export function defineOverride<T extends ITask<any, any, any, any>>(
|
|
317
|
+
base: T,
|
|
318
|
+
patch: Omit<Partial<T>, "id">
|
|
319
|
+
): T;
|
|
320
|
+
export function defineOverride<T extends IResource<any, any, any, any>>(
|
|
321
|
+
base: T,
|
|
322
|
+
patch: Omit<Partial<T>, "id">
|
|
323
|
+
): T;
|
|
324
|
+
export function defineOverride<T extends IMiddleware<any, any>>(
|
|
325
|
+
base: T,
|
|
326
|
+
patch: Omit<Partial<T>, "id">
|
|
327
|
+
): T;
|
|
328
|
+
export function defineOverride(
|
|
329
|
+
base: ITask | IResource | IMiddleware,
|
|
330
|
+
patch: Record<string | symbol, unknown>
|
|
331
|
+
): ITask | IResource | IMiddleware {
|
|
332
|
+
const { id: _ignored, ...rest } = (patch || {}) as any;
|
|
333
|
+
// Ensure we never change the id, and merge overrides last
|
|
334
|
+
return {
|
|
335
|
+
...(base as any),
|
|
336
|
+
...rest,
|
|
337
|
+
id: (base as any).id,
|
|
338
|
+
} as any;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Creates a tag definition.
|
|
343
|
+
* - `.with(config)` to create configured instances
|
|
344
|
+
* - `.extract(tags)` to extract this tag from a list of tags
|
|
345
|
+
*/
|
|
346
|
+
export function defineTag<TConfig = void>(
|
|
347
|
+
definition: ITagDefinition<TConfig>
|
|
348
|
+
): ITag<TConfig> {
|
|
349
|
+
const id = definition.id;
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
id,
|
|
353
|
+
with(tagConfig: TConfig) {
|
|
354
|
+
return {
|
|
355
|
+
id,
|
|
356
|
+
tag: this,
|
|
357
|
+
config: tagConfig as any,
|
|
358
|
+
} as ITagWithConfig<TConfig>;
|
|
359
|
+
},
|
|
360
|
+
extract(target: TagType[] | ITaggable) {
|
|
361
|
+
const tags = Array.isArray(target) ? target : target?.meta?.tags || [];
|
|
362
|
+
for (const candidate of tags) {
|
|
363
|
+
if (typeof candidate === "string") continue;
|
|
364
|
+
// Configured instance
|
|
365
|
+
if (candidate.id === id) {
|
|
366
|
+
return candidate as ITagWithConfig<TConfig>;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
},
|
|
371
|
+
} as ITag<TConfig>;
|
|
268
372
|
}
|