@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
package/src/defs.ts
CHANGED
|
@@ -1,8 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Core public TypeScript types for BlueLibs Runner.
|
|
3
|
+
*
|
|
4
|
+
* This file contains the strongly-typed contract for tasks, resources, events
|
|
5
|
+
* and middleware. It mirrors the mental model described in the README:
|
|
6
|
+
* - Tasks are functions (with lifecycle events)
|
|
7
|
+
* - Resources are singletons (with init/dispose hooks and lifecycle events)
|
|
8
|
+
* - Events are simple, strongly-typed emissions
|
|
9
|
+
* - Middleware can target both tasks and resources
|
|
10
|
+
*
|
|
11
|
+
* DX goals:
|
|
12
|
+
* - Crystal‑clear generics and helper types that infer dependency shapes
|
|
13
|
+
* - Friendly JSDoc you can hover in editors to understand usage instantly
|
|
14
|
+
* - Safe overrides and strong typing around config and register mechanics
|
|
15
|
+
*/
|
|
16
|
+
|
|
2
17
|
import { MiddlewareEverywhereOptions } from "./define";
|
|
3
18
|
|
|
19
|
+
// Re-export public cache type so consumers don’t import from internals.
|
|
4
20
|
export { ICacheInstance } from "./globals/middleware/cache.middleware";
|
|
5
|
-
|
|
21
|
+
export * from "./models/StoreTypes";
|
|
22
|
+
/**
|
|
23
|
+
* Internal brand symbols used to tag created objects at runtime and help with
|
|
24
|
+
* type‑narrowing. Prefer the `isTask`/`isResource`/`isEvent`/`isMiddleware`
|
|
25
|
+
* helpers instead of touching these directly.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
6
28
|
export const symbolTask: unique symbol = Symbol("runner.task");
|
|
7
29
|
export const symbolResource: unique symbol = Symbol("runner.resource");
|
|
8
30
|
export const symbolResourceWithConfig: unique symbol = Symbol(
|
|
@@ -23,32 +45,89 @@ export const symbolMiddlewareEverywhereResources: unique symbol = Symbol(
|
|
|
23
45
|
"runner.middlewareGlobalResources"
|
|
24
46
|
);
|
|
25
47
|
|
|
48
|
+
/** @internal Path to aid anonymous id generation and error messages */
|
|
26
49
|
export const symbolFilePath: unique symbol = Symbol("runner.filePath");
|
|
50
|
+
/** @internal Marks disposable instances */
|
|
27
51
|
export const symbolDispose: unique symbol = Symbol("runner.dispose");
|
|
52
|
+
/** @internal Link to internal Store */
|
|
28
53
|
export const symbolStore: unique symbol = Symbol("runner.store");
|
|
29
54
|
|
|
55
|
+
/** @internal Brand used by index() resources */
|
|
30
56
|
export const symbolIndexResource: unique symbol = Symbol(
|
|
31
57
|
"runner.indexResource"
|
|
32
58
|
);
|
|
33
59
|
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
60
|
+
export interface ITagDefinition<TConfig = void> {
|
|
61
|
+
id: string | symbol;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A configured instance of a tag as produced by `ITag.with()`.
|
|
66
|
+
*/
|
|
67
|
+
export interface ITagWithConfig<TConfig = void> {
|
|
68
|
+
id: string | symbol;
|
|
69
|
+
/** The tag definition used to produce this configured instance. */
|
|
70
|
+
tag: ITag<TConfig>;
|
|
71
|
+
/** The configuration captured for this tag instance. */
|
|
72
|
+
config: TConfig;
|
|
73
|
+
}
|
|
47
74
|
|
|
75
|
+
/**
|
|
76
|
+
* A tag definition (builder). Use `.with(config)` to obtain configured instances,
|
|
77
|
+
* and `.extract(tags)` to find either a configured instance or the bare tag in a list.
|
|
78
|
+
*/
|
|
79
|
+
export interface ITag<TConfig = void> extends ITagDefinition<TConfig> {
|
|
80
|
+
/**
|
|
81
|
+
* Creates a configured instance of the tag.
|
|
82
|
+
*/
|
|
83
|
+
with(config: TConfig): ITagWithConfig<TConfig>;
|
|
84
|
+
/**
|
|
85
|
+
* Extracts either a configured instance or the bare tag from a list of tags
|
|
86
|
+
* or from a taggable object (`{ meta: { tags?: [] } }`).
|
|
87
|
+
*/
|
|
88
|
+
extract(target: TagType[] | ITaggable): ExtractedTagResult<TConfig> | null;
|
|
89
|
+
[symbolFilePath]: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Restrict bare tags to those whose config can be omitted (void or optional object),
|
|
94
|
+
* mirroring the same principle used for resources in `RegisterableItems`.
|
|
95
|
+
* Required-config tags must appear as configured instances.
|
|
96
|
+
*/
|
|
97
|
+
export type TagType =
|
|
98
|
+
| string
|
|
99
|
+
| ITag<void>
|
|
100
|
+
| ITag<{ [K in any]?: any }>
|
|
101
|
+
| ITagWithConfig<any>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Conditional result type for `ITag.extract`:
|
|
105
|
+
* - For void config → just the identifier
|
|
106
|
+
* - For optional object config → identifier with optional config
|
|
107
|
+
* - For required config → identifier with required config
|
|
108
|
+
*/
|
|
109
|
+
export type ExtractedTagResult<TConfig> = {} extends TConfig
|
|
110
|
+
? { id: string | symbol; config?: TConfig }
|
|
111
|
+
: { id: string | symbol; config: TConfig };
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Any object that can carry tags via metadata. This mirrors how tasks,
|
|
115
|
+
* resources, events, and middleware expose `meta.tags`.
|
|
116
|
+
*/
|
|
117
|
+
export interface ITaggable {
|
|
118
|
+
meta?: {
|
|
119
|
+
tags?: TagType[];
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Common metadata you can attach to tasks/resources/events/middleware.
|
|
125
|
+
* Useful for docs, filtering and middleware decisions.
|
|
126
|
+
*/
|
|
48
127
|
export interface IMeta {
|
|
49
128
|
title?: string;
|
|
50
129
|
description?: string;
|
|
51
|
-
tags?:
|
|
130
|
+
tags?: TagType[];
|
|
52
131
|
}
|
|
53
132
|
|
|
54
133
|
export interface ITaskMeta extends IMeta {}
|
|
@@ -56,7 +135,11 @@ export interface IResourceMeta extends IMeta {}
|
|
|
56
135
|
export interface IEventMeta extends IMeta {}
|
|
57
136
|
export interface IMiddlewareMeta extends IMeta {}
|
|
58
137
|
|
|
59
|
-
|
|
138
|
+
/**
|
|
139
|
+
* A mapping of dependency keys to Runner definitions. Used in `dependencies`
|
|
140
|
+
* for tasks and resources. Values are later transformed into the actual
|
|
141
|
+
* callable/value shape by `DependencyValuesType`.
|
|
142
|
+
*/
|
|
60
143
|
export type DependencyMapType = Record<
|
|
61
144
|
string,
|
|
62
145
|
ITask<any, any, any, any> | IResource<any, any, any> | IEventDefinition<any>
|
|
@@ -72,21 +155,28 @@ type ExtractResourceValue<T> = T extends IResource<any, infer V, infer D>
|
|
|
72
155
|
type ExtractEventParams<T> = T extends IEvent<infer P> ? P : never;
|
|
73
156
|
|
|
74
157
|
/**
|
|
75
|
-
*
|
|
158
|
+
* Task dependencies transform into callable functions: call with the task input
|
|
159
|
+
* and you receive the task output.
|
|
76
160
|
*/
|
|
77
161
|
type TaskDependency<I, O> = (...args: I extends null | void ? [] : [I]) => O;
|
|
78
162
|
/**
|
|
79
|
-
*
|
|
163
|
+
* Resource dependencies resolve to the resource's value directly.
|
|
80
164
|
*/
|
|
81
165
|
type ResourceDependency<V> = V;
|
|
82
166
|
/**
|
|
83
|
-
*
|
|
167
|
+
* Event dependencies resolve to an emitter function. If the payload type is
|
|
168
|
+
* `void`, the function can be called with zero args (or an empty object).
|
|
84
169
|
*/
|
|
85
170
|
type EventDependency<P> = P extends void
|
|
86
171
|
? (() => Promise<void>) & ((input?: Record<string, never>) => Promise<void>)
|
|
87
172
|
: (input: P) => Promise<void>;
|
|
88
173
|
|
|
89
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Transforms a dependency definition into the usable shape inside `run`/`init`:
|
|
176
|
+
* - Task -> callable function
|
|
177
|
+
* - Resource -> resolved value
|
|
178
|
+
* - Event -> emit function
|
|
179
|
+
*/
|
|
90
180
|
export type DependencyValueType<T> = T extends ITask<any, any, any>
|
|
91
181
|
? TaskDependency<ExtractTaskInput<T>, ExtractTaskOutput<T>>
|
|
92
182
|
: T extends IResource<any, any>
|
|
@@ -99,7 +189,13 @@ export type DependencyValuesType<T extends DependencyMapType> = {
|
|
|
99
189
|
[K in keyof T]: DependencyValueType<T[K]>;
|
|
100
190
|
};
|
|
101
191
|
|
|
102
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Anything you can put inside a resource's `register: []`.
|
|
194
|
+
* - Resources (with or without `.with()`)
|
|
195
|
+
* - Tasks
|
|
196
|
+
* - Middleware
|
|
197
|
+
* - Events
|
|
198
|
+
*/
|
|
103
199
|
export type RegisterableItems<T = any> =
|
|
104
200
|
| IResourceWithConfig<any>
|
|
105
201
|
| IResource<void, any, any, any> // For void configs
|
|
@@ -119,8 +215,17 @@ export interface ITaskDefinition<
|
|
|
119
215
|
TDependencies extends DependencyMapType = {},
|
|
120
216
|
TOn extends "*" | IEventDefinition<any> | undefined = undefined // Adding a generic to track 'on' type
|
|
121
217
|
> {
|
|
218
|
+
/**
|
|
219
|
+
* Stable identifier. If omitted, an anonymous id is generated from file path
|
|
220
|
+
* (see README: Anonymous IDs).
|
|
221
|
+
*/
|
|
122
222
|
id?: string | symbol;
|
|
223
|
+
/**
|
|
224
|
+
* Access other tasks/resources/events. Can be an object or a function when
|
|
225
|
+
* you need late or config‑dependent resolution.
|
|
226
|
+
*/
|
|
123
227
|
dependencies?: TDependencies | (() => TDependencies);
|
|
228
|
+
/** Middleware applied around task execution. */
|
|
124
229
|
middleware?: MiddlewareAttachments[];
|
|
125
230
|
/**
|
|
126
231
|
* Listen to events in a simple way
|
|
@@ -131,7 +236,12 @@ export interface ITaskDefinition<
|
|
|
131
236
|
* The event with the lowest order will be executed first.
|
|
132
237
|
*/
|
|
133
238
|
listenerOrder?: number;
|
|
239
|
+
/** Optional metadata used for docs, filtering and tooling. */
|
|
134
240
|
meta?: ITaskMeta;
|
|
241
|
+
/**
|
|
242
|
+
* The task body. If `on` is set, the input is an `IEventEmission`. Otherwise,
|
|
243
|
+
* it's the declared input type.
|
|
244
|
+
*/
|
|
135
245
|
run: (
|
|
136
246
|
input: TOn extends undefined
|
|
137
247
|
? TInput
|
|
@@ -188,6 +298,8 @@ export interface ITask<
|
|
|
188
298
|
afterRun: IEvent<AfterRunEventPayload<TInput, TOutput>>;
|
|
189
299
|
onError: IEvent<OnErrorEventPayload>;
|
|
190
300
|
};
|
|
301
|
+
[symbolFilePath]: string;
|
|
302
|
+
[symbolTask]: true;
|
|
191
303
|
}
|
|
192
304
|
|
|
193
305
|
export interface IResourceDefinition<
|
|
@@ -198,11 +310,20 @@ export interface IResourceDefinition<
|
|
|
198
310
|
THooks = any,
|
|
199
311
|
TRegisterableItems = any
|
|
200
312
|
> {
|
|
313
|
+
/** Stable identifier. Omit to get an anonymous id. */
|
|
201
314
|
id?: string | symbol;
|
|
315
|
+
/** Static or lazy dependency map. Receives `config` when provided. */
|
|
202
316
|
dependencies?: TDependencies | ((config: TConfig) => TDependencies);
|
|
317
|
+
/**
|
|
318
|
+
* Register other registerables (resources/tasks/middleware/events). Accepts a
|
|
319
|
+
* static array or a function of `config` to support dynamic wiring.
|
|
320
|
+
*/
|
|
203
321
|
register?:
|
|
204
322
|
| Array<RegisterableItems>
|
|
205
323
|
| ((config: TConfig) => Array<RegisterableItems>);
|
|
324
|
+
/**
|
|
325
|
+
* Initialize and return the resource value. Called once during boot.
|
|
326
|
+
*/
|
|
206
327
|
init?: (
|
|
207
328
|
this: any,
|
|
208
329
|
config: TConfig,
|
|
@@ -225,11 +346,20 @@ export interface IResourceDefinition<
|
|
|
225
346
|
context: TContext
|
|
226
347
|
) => Promise<void>;
|
|
227
348
|
meta?: IResourceMeta;
|
|
349
|
+
/**
|
|
350
|
+
* Safe overrides to swap behavior while preserving identities. See
|
|
351
|
+
* README: Overrides.
|
|
352
|
+
*/
|
|
228
353
|
overrides?: Array<IResource | ITask | IMiddleware | IResourceWithConfig>;
|
|
354
|
+
/** Middleware applied around init/dispose. */
|
|
229
355
|
middleware?: MiddlewareAttachments[];
|
|
356
|
+
/**
|
|
357
|
+
* Create a private, mutable context shared between `init` and `dispose`.
|
|
358
|
+
*/
|
|
230
359
|
context?: () => TContext;
|
|
231
360
|
/**
|
|
232
361
|
* This is optional and used from an index resource to get the correct caller.
|
|
362
|
+
* This is the reason we allow it here as well.
|
|
233
363
|
*/
|
|
234
364
|
[symbolFilePath]?: string;
|
|
235
365
|
/**
|
|
@@ -259,6 +389,9 @@ export interface IResource<
|
|
|
259
389
|
};
|
|
260
390
|
overrides: Array<IResource | ITask | IMiddleware | IResourceWithConfig>;
|
|
261
391
|
middleware: MiddlewareAttachments[];
|
|
392
|
+
[symbolFilePath]: string;
|
|
393
|
+
[symbolIndexResource]: boolean;
|
|
394
|
+
[symbolResource]: true;
|
|
262
395
|
}
|
|
263
396
|
|
|
264
397
|
export interface IResourceWithConfig<
|
|
@@ -266,8 +399,11 @@ export interface IResourceWithConfig<
|
|
|
266
399
|
TValue = any,
|
|
267
400
|
TDependencies extends DependencyMapType = any
|
|
268
401
|
> {
|
|
402
|
+
/** The id of the underlying resource. */
|
|
269
403
|
id: string;
|
|
404
|
+
/** The underlying resource definition. */
|
|
270
405
|
resource: IResource<TConfig, TValue, TDependencies>;
|
|
406
|
+
/** The configuration captured by `.with(config)`. */
|
|
271
407
|
config: TConfig;
|
|
272
408
|
}
|
|
273
409
|
|
|
@@ -276,6 +412,7 @@ export type EventHandlerType<T = any> = (
|
|
|
276
412
|
) => any | Promise<any>;
|
|
277
413
|
|
|
278
414
|
export interface IEventDefinition<TPayload = void> {
|
|
415
|
+
/** Stable identifier. Omit to get an anonymous id. */
|
|
279
416
|
id?: string | symbol;
|
|
280
417
|
meta?: IEventMeta;
|
|
281
418
|
}
|
|
@@ -286,6 +423,7 @@ export interface IEvent<TPayload = any> extends IEventDefinition<TPayload> {
|
|
|
286
423
|
* We use this event to discriminate between resources with just 'id' and 'events' as they collide. This is a workaround, should be redone using classes and instanceof.
|
|
287
424
|
*/
|
|
288
425
|
[symbolEvent]: true;
|
|
426
|
+
[symbolFilePath]: string;
|
|
289
427
|
}
|
|
290
428
|
|
|
291
429
|
/**
|
|
@@ -327,8 +465,13 @@ export interface IMiddlewareDefinition<
|
|
|
327
465
|
TConfig = any,
|
|
328
466
|
TDependencies extends DependencyMapType = any
|
|
329
467
|
> {
|
|
468
|
+
/** Stable identifier. Omit to get an anonymous id. */
|
|
330
469
|
id?: string | symbol;
|
|
470
|
+
/** Static or lazy dependency map. */
|
|
331
471
|
dependencies?: TDependencies | ((config: TConfig) => TDependencies);
|
|
472
|
+
/**
|
|
473
|
+
* The middleware body, called with task/resource execution input.
|
|
474
|
+
*/
|
|
332
475
|
run: (
|
|
333
476
|
input: IMiddlewareExecutionInput,
|
|
334
477
|
dependencies: DependencyValuesType<TDependencies>,
|
|
@@ -348,11 +491,18 @@ export interface IMiddleware<
|
|
|
348
491
|
|
|
349
492
|
id: string | symbol;
|
|
350
493
|
dependencies: TDependencies | (() => TDependencies);
|
|
494
|
+
/**
|
|
495
|
+
* Attach this middleware globally. Use options to scope to tasks/resources.
|
|
496
|
+
*/
|
|
351
497
|
everywhere(
|
|
352
498
|
config?: MiddlewareEverywhereOptions
|
|
353
499
|
): IMiddleware<TConfig, TDependencies>;
|
|
500
|
+
/** Current configuration object (empty by default). */
|
|
354
501
|
config: TConfig;
|
|
502
|
+
/** Configure the middleware and return a marked, configured instance. */
|
|
355
503
|
with: (config: TConfig) => IMiddlewareConfigured<TConfig, TDependencies>;
|
|
504
|
+
[symbolFilePath]: string;
|
|
505
|
+
[symbolMiddleware]: true;
|
|
356
506
|
}
|
|
357
507
|
|
|
358
508
|
export interface IMiddlewareConfigured<
|
|
@@ -373,10 +523,12 @@ export interface IMiddlewareExecutionInput<
|
|
|
373
523
|
TTaskInput = any,
|
|
374
524
|
TResourceConfig = any
|
|
375
525
|
> {
|
|
526
|
+
/** Task hook: present when wrapping a task run. */
|
|
376
527
|
task?: {
|
|
377
528
|
definition: ITask<TTaskInput>;
|
|
378
529
|
input: TTaskInput;
|
|
379
530
|
};
|
|
531
|
+
/** Resource hook: present when wrapping init/dispose. */
|
|
380
532
|
resource?: {
|
|
381
533
|
definition: IResource<TResourceConfig>;
|
|
382
534
|
config: TResourceConfig;
|
|
@@ -3,6 +3,7 @@ import { defineMiddleware } from "../define";
|
|
|
3
3
|
import { cacheMiddleware } from "./middleware/cache.middleware";
|
|
4
4
|
import { requireContextMiddleware } from "./middleware/requireContext.middleware";
|
|
5
5
|
import { retryMiddleware } from "./middleware/retry.middleware";
|
|
6
|
+
import { timeoutMiddleware } from "./middleware/timeout.middleware";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Global middlewares
|
|
@@ -11,4 +12,5 @@ export const globalMiddlewares = {
|
|
|
11
12
|
requireContext: requireContextMiddleware,
|
|
12
13
|
retry: retryMiddleware,
|
|
13
14
|
cache: cacheMiddleware,
|
|
15
|
+
timeout: timeoutMiddleware,
|
|
14
16
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defineMiddleware } from "../../define";
|
|
2
|
+
|
|
3
|
+
export interface TimeoutMiddlewareConfig {
|
|
4
|
+
/**
|
|
5
|
+
* Maximum time in milliseconds before the wrapped operation is aborted
|
|
6
|
+
* and a timeout error is thrown. Defaults to 5000ms.
|
|
7
|
+
*/
|
|
8
|
+
ttl: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const timeoutMiddleware = defineMiddleware({
|
|
12
|
+
id: "globals.middleware.timeout",
|
|
13
|
+
async run({ task, resource, next }, _deps, config: TimeoutMiddlewareConfig) {
|
|
14
|
+
const input = task ? task.input : resource?.config;
|
|
15
|
+
|
|
16
|
+
const ttl = Math.max(0, config.ttl);
|
|
17
|
+
const message = `Operation timed out after ${ttl}ms`;
|
|
18
|
+
|
|
19
|
+
// Fast-path: immediate timeout
|
|
20
|
+
if (ttl === 0) {
|
|
21
|
+
const error = new Error(message);
|
|
22
|
+
(error as any).name = "TimeoutError";
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
|
|
28
|
+
// Create a timeout promise that rejects when aborted
|
|
29
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
30
|
+
const timeoutId = setTimeout(() => {
|
|
31
|
+
controller.abort();
|
|
32
|
+
const error = new Error(message);
|
|
33
|
+
(error as any).name = "TimeoutError";
|
|
34
|
+
reject(error);
|
|
35
|
+
}, ttl);
|
|
36
|
+
|
|
37
|
+
// Clean up timeout if abort signal fires for other reasons
|
|
38
|
+
controller.signal.addEventListener("abort", () => {
|
|
39
|
+
clearTimeout(timeoutId);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Race between the actual operation and the timeout
|
|
44
|
+
return Promise.race([next(input), timeoutPromise]);
|
|
45
|
+
},
|
|
46
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -4,12 +4,15 @@ import {
|
|
|
4
4
|
defineEvent,
|
|
5
5
|
defineMiddleware,
|
|
6
6
|
defineIndex,
|
|
7
|
+
defineTag,
|
|
8
|
+
defineOverride,
|
|
7
9
|
} from "./define";
|
|
8
10
|
import { createContext } from "./context";
|
|
9
11
|
import { globalEvents } from "./globals/globalEvents";
|
|
10
12
|
import { globalResources } from "./globals/globalResources";
|
|
11
13
|
import { globalMiddlewares } from "./globals/globalMiddleware";
|
|
12
14
|
import { run } from "./run";
|
|
15
|
+
import { createTestResource } from "./testing";
|
|
13
16
|
|
|
14
17
|
const globals = {
|
|
15
18
|
events: globalEvents,
|
|
@@ -24,8 +27,11 @@ export {
|
|
|
24
27
|
defineEvent as event,
|
|
25
28
|
defineMiddleware as middleware,
|
|
26
29
|
defineIndex as index,
|
|
30
|
+
defineTag as tag,
|
|
31
|
+
defineOverride as override,
|
|
27
32
|
run,
|
|
28
33
|
createContext,
|
|
34
|
+
createTestResource,
|
|
29
35
|
};
|
|
30
36
|
|
|
31
37
|
export * as definitions from "./defs";
|
|
@@ -139,11 +139,7 @@ export class DependencyProcessor {
|
|
|
139
139
|
return;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
return this.taskRunner.run(
|
|
143
|
-
task.task,
|
|
144
|
-
receivedEvent,
|
|
145
|
-
task.computedDependencies
|
|
146
|
-
);
|
|
142
|
+
return this.taskRunner.run(task.task, receivedEvent);
|
|
147
143
|
};
|
|
148
144
|
|
|
149
145
|
if (eventDefinition === "*") {
|
|
@@ -217,11 +213,7 @@ export class DependencyProcessor {
|
|
|
217
213
|
}
|
|
218
214
|
|
|
219
215
|
return (input: any) => {
|
|
220
|
-
return this.taskRunner.run(
|
|
221
|
-
storeTask.task,
|
|
222
|
-
input,
|
|
223
|
-
storeTask.computedDependencies
|
|
224
|
-
);
|
|
216
|
+
return this.taskRunner.run(storeTask.task, input);
|
|
225
217
|
};
|
|
226
218
|
}
|
|
227
219
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { globalResources } from "../globals/globalResources";
|
|
2
2
|
import { requireContextMiddleware } from "../globals/middleware/requireContext.middleware";
|
|
3
3
|
import { retryMiddleware } from "../globals/middleware/retry.middleware";
|
|
4
|
+
import { timeoutMiddleware } from "../globals/middleware/timeout.middleware";
|
|
4
5
|
import { EventManager } from "./EventManager";
|
|
5
6
|
import { Store } from "./Store";
|
|
6
7
|
|
|
@@ -13,5 +14,5 @@ export function getBuiltInResources(eventManager: EventManager, store: Store) {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export function getBuiltInMiddlewares() {
|
|
16
|
-
return [requireContextMiddleware, retryMiddleware];
|
|
17
|
+
return [requireContextMiddleware, retryMiddleware, timeoutMiddleware];
|
|
17
18
|
}
|
package/src/models/TaskRunner.ts
CHANGED
|
@@ -23,7 +23,6 @@ export class TaskRunner {
|
|
|
23
23
|
* This function can throw only if any of the event listeners or run function throws
|
|
24
24
|
* @param task the task to be run
|
|
25
25
|
* @param input the input to be passed to the task
|
|
26
|
-
* @param taskDependencies optional dependencies to be passed to the task, if not provided, the dependencies will be the ones already computed from the store.
|
|
27
26
|
*/
|
|
28
27
|
public async run<
|
|
29
28
|
TInput,
|
|
@@ -31,8 +30,7 @@ export class TaskRunner {
|
|
|
31
30
|
TDeps extends DependencyMapType
|
|
32
31
|
>(
|
|
33
32
|
task: ITask<TInput, TOutput, TDeps>,
|
|
34
|
-
input: TInput
|
|
35
|
-
taskDependencies?: DependencyValuesType<TDeps>
|
|
33
|
+
input: TInput
|
|
36
34
|
): Promise<TOutput | undefined> {
|
|
37
35
|
let runner = this.runnerStore.get(task.id);
|
|
38
36
|
if (!runner) {
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineResource } from "./define";
|
|
2
|
+
import { globalResources } from "./globals/globalResources";
|
|
3
|
+
import {
|
|
4
|
+
IResource,
|
|
5
|
+
IResourceWithConfig,
|
|
6
|
+
IMiddleware,
|
|
7
|
+
ITask,
|
|
8
|
+
RegisterableItems,
|
|
9
|
+
IEvent,
|
|
10
|
+
IEventEmission,
|
|
11
|
+
DependencyMapType,
|
|
12
|
+
DependencyValuesType,
|
|
13
|
+
} from "./defs";
|
|
14
|
+
import { EventManager, Logger, Store, TaskRunner } from "./models";
|
|
15
|
+
|
|
16
|
+
let testResourceCounter = 0;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Helper to create a minimal test harness resource that wraps a root app (or any registerable)
|
|
20
|
+
* and exposes convenient testing utilities while running the full ecosystem
|
|
21
|
+
* (registration, overrides, middleware, events) without modifying the core API.
|
|
22
|
+
*/
|
|
23
|
+
export function createTestResource(
|
|
24
|
+
root: RegisterableItems,
|
|
25
|
+
options?: {
|
|
26
|
+
overrides?: Array<IResource | ITask | IMiddleware | IResourceWithConfig>;
|
|
27
|
+
}
|
|
28
|
+
): IResource<void, ReturnType<typeof buildTestFacade>> {
|
|
29
|
+
return defineResource({
|
|
30
|
+
id: `tests.createTestResource.${++testResourceCounter}`,
|
|
31
|
+
register: [root],
|
|
32
|
+
overrides: options?.overrides || [],
|
|
33
|
+
dependencies: {
|
|
34
|
+
taskRunner: globalResources.taskRunner,
|
|
35
|
+
store: globalResources.store,
|
|
36
|
+
logger: globalResources.logger,
|
|
37
|
+
eventManager: globalResources.eventManager,
|
|
38
|
+
},
|
|
39
|
+
async init(_, deps) {
|
|
40
|
+
return buildTestFacade(deps);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildTestFacade(deps: {
|
|
46
|
+
taskRunner: TaskRunner;
|
|
47
|
+
store: Store;
|
|
48
|
+
logger: Logger;
|
|
49
|
+
eventManager: EventManager;
|
|
50
|
+
}) {
|
|
51
|
+
return {
|
|
52
|
+
// Run a task within the fully initialized ecosystem
|
|
53
|
+
runTask: <I, O extends Promise<any>, D extends DependencyMapType>(
|
|
54
|
+
task: ITask<I, O, D>,
|
|
55
|
+
input: I
|
|
56
|
+
): Promise<Awaited<O> | undefined> =>
|
|
57
|
+
deps.taskRunner.run(task, input) as any,
|
|
58
|
+
// Access a resource value by id (string or symbol)
|
|
59
|
+
getResource: (id: string | symbol) => deps.store.resources.get(id)?.value,
|
|
60
|
+
// Expose internals when needed in tests (not recommended for app usage)
|
|
61
|
+
taskRunner: deps.taskRunner,
|
|
62
|
+
store: deps.store,
|
|
63
|
+
logger: deps.logger,
|
|
64
|
+
eventManager: deps.eventManager,
|
|
65
|
+
};
|
|
66
|
+
}
|