@bluelibs/runner 3.3.2 → 3.4.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 +491 -74
- package/dist/define.d.ts +5 -5
- package/dist/define.js +22 -2
- package/dist/define.js.map +1 -1
- package/dist/defs.d.ts +55 -21
- package/dist/defs.js.map +1 -1
- package/dist/defs.returnTag.d.ts +36 -0
- package/dist/defs.returnTag.js +4 -0
- package/dist/defs.returnTag.js.map +1 -0
- package/dist/errors.d.ts +60 -10
- package/dist/errors.js +103 -12
- package/dist/errors.js.map +1 -1
- package/dist/globals/globalMiddleware.d.ts +4 -4
- package/dist/globals/globalResources.d.ts +28 -10
- package/dist/globals/middleware/cache.middleware.d.ts +9 -9
- package/dist/globals/resources/queue.resource.d.ts +5 -2
- package/dist/index.d.ts +33 -14
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/models/DependencyProcessor.js +4 -4
- package/dist/models/DependencyProcessor.js.map +1 -1
- package/dist/models/EventManager.js +10 -1
- package/dist/models/EventManager.js.map +1 -1
- package/dist/models/Logger.d.ts +8 -0
- package/dist/models/Logger.js +24 -0
- package/dist/models/Logger.js.map +1 -1
- package/dist/models/OverrideManager.js +1 -1
- package/dist/models/OverrideManager.js.map +1 -1
- package/dist/models/ResourceInitializer.d.ts +2 -2
- package/dist/models/ResourceInitializer.js.map +1 -1
- package/dist/models/Store.d.ts +5 -3
- package/dist/models/Store.js +7 -1
- package/dist/models/Store.js.map +1 -1
- package/dist/models/StoreConstants.d.ts +6 -3
- package/dist/models/StoreRegistry.d.ts +5 -3
- package/dist/models/StoreRegistry.js +17 -1
- package/dist/models/StoreRegistry.js.map +1 -1
- package/dist/models/StoreTypes.d.ts +1 -1
- package/dist/models/StoreValidator.js +5 -5
- package/dist/models/StoreValidator.js.map +1 -1
- package/dist/models/TaskRunner.js +10 -0
- package/dist/models/TaskRunner.js.map +1 -1
- package/dist/run.d.ts +3 -3
- package/dist/run.js +1 -1
- package/dist/run.js.map +1 -1
- package/dist/t1.d.ts +1 -0
- package/dist/t1.js +13 -0
- package/dist/t1.js.map +1 -0
- package/dist/testing.d.ts +1 -1
- package/package.json +2 -2
- package/src/__tests__/errors.test.ts +92 -11
- package/src/__tests__/models/EventManager.test.ts +0 -1
- package/src/__tests__/models/Logger.test.ts +82 -5
- package/src/__tests__/models/Store.test.ts +57 -0
- package/src/__tests__/recursion/c.resource.ts +1 -1
- package/src/__tests__/run.overrides.test.ts +3 -3
- package/src/__tests__/typesafety.test.ts +112 -9
- package/src/__tests__/validation-edge-cases.test.ts +111 -0
- package/src/__tests__/validation-interface.test.ts +428 -0
- package/src/define.ts +47 -15
- package/src/defs.returnTag.ts +91 -0
- package/src/defs.ts +84 -27
- package/src/errors.ts +95 -23
- package/src/index.ts +1 -0
- package/src/models/DependencyProcessor.ts +9 -5
- package/src/models/EventManager.ts +12 -3
- package/src/models/Logger.ts +28 -0
- package/src/models/OverrideManager.ts +2 -7
- package/src/models/ResourceInitializer.ts +8 -3
- package/src/models/Store.ts +12 -3
- package/src/models/StoreRegistry.ts +27 -2
- package/src/models/StoreTypes.ts +1 -1
- package/src/models/StoreValidator.ts +6 -6
- package/src/models/TaskRunner.ts +10 -1
- package/src/run.ts +8 -5
- package/src/testing.ts +1 -1
package/src/define.ts
CHANGED
|
@@ -33,8 +33,10 @@ import {
|
|
|
33
33
|
symbolResourceWithConfig,
|
|
34
34
|
symbolResource,
|
|
35
35
|
symbolMiddleware,
|
|
36
|
+
ITaskMeta,
|
|
37
|
+
IResourceMeta,
|
|
36
38
|
} from "./defs";
|
|
37
|
-
import {
|
|
39
|
+
import { MiddlewareAlreadyGlobalError, ValidationError } from "./errors";
|
|
38
40
|
import { generateCallerIdFromFile, getCallerFile } from "./tools/getCallerFile";
|
|
39
41
|
|
|
40
42
|
// Helper function to get the caller file
|
|
@@ -43,10 +45,11 @@ export function defineTask<
|
|
|
43
45
|
Input = undefined,
|
|
44
46
|
Output extends Promise<any> = any,
|
|
45
47
|
Deps extends DependencyMapType = any,
|
|
46
|
-
TOn extends "*" | IEventDefinition | undefined = undefined
|
|
48
|
+
TOn extends "*" | IEventDefinition | undefined = undefined,
|
|
49
|
+
TMeta extends ITaskMeta = any
|
|
47
50
|
>(
|
|
48
|
-
taskConfig: ITaskDefinition<Input, Output, Deps, TOn>
|
|
49
|
-
): ITask<Input, Output, Deps, TOn> {
|
|
51
|
+
taskConfig: ITaskDefinition<Input, Output, Deps, TOn, TMeta>
|
|
52
|
+
): ITask<Input, Output, Deps, TOn, TMeta> {
|
|
50
53
|
/**
|
|
51
54
|
* Creates a task definition.
|
|
52
55
|
* - Generates an anonymous id based on file path when `id` is omitted
|
|
@@ -65,6 +68,7 @@ export function defineTask<
|
|
|
65
68
|
run: taskConfig.run,
|
|
66
69
|
on: taskConfig.on,
|
|
67
70
|
listenerOrder: taskConfig.listenerOrder,
|
|
71
|
+
inputSchema: taskConfig.inputSchema,
|
|
68
72
|
events: {
|
|
69
73
|
beforeRun: {
|
|
70
74
|
...defineEvent({
|
|
@@ -91,19 +95,28 @@ export function defineTask<
|
|
|
91
95
|
[symbolFilePath]: getCallerFile(),
|
|
92
96
|
},
|
|
93
97
|
},
|
|
94
|
-
meta: taskConfig.meta || {},
|
|
98
|
+
meta: taskConfig.meta || ({} as TMeta),
|
|
95
99
|
// autorun,
|
|
96
100
|
};
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
export function defineResource<
|
|
100
104
|
TConfig = void,
|
|
101
|
-
TValue = any
|
|
105
|
+
TValue extends Promise<any> = Promise<any>,
|
|
102
106
|
TDeps extends DependencyMapType = {},
|
|
103
|
-
TPrivate = any
|
|
107
|
+
TPrivate = any,
|
|
108
|
+
TMeta extends IResourceMeta = any
|
|
104
109
|
>(
|
|
105
|
-
constConfig: IResourceDefinition<
|
|
106
|
-
|
|
110
|
+
constConfig: IResourceDefinition<
|
|
111
|
+
TConfig,
|
|
112
|
+
TValue,
|
|
113
|
+
TDeps,
|
|
114
|
+
TPrivate,
|
|
115
|
+
any,
|
|
116
|
+
any,
|
|
117
|
+
TMeta
|
|
118
|
+
>
|
|
119
|
+
): IResource<TConfig, TValue, TDeps, TPrivate, TMeta> {
|
|
107
120
|
/**
|
|
108
121
|
* Creates a resource definition.
|
|
109
122
|
* - Generates anonymous id when omitted (resource or index flavor)
|
|
@@ -129,7 +142,17 @@ export function defineResource<
|
|
|
129
142
|
overrides: constConfig.overrides || [],
|
|
130
143
|
init: constConfig.init,
|
|
131
144
|
context: constConfig.context,
|
|
145
|
+
configSchema: constConfig.configSchema,
|
|
132
146
|
with: function (config: TConfig) {
|
|
147
|
+
// Validate config with schema if provided (fail fast)
|
|
148
|
+
if (this.configSchema) {
|
|
149
|
+
try {
|
|
150
|
+
config = this.configSchema.parse(config);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw new ValidationError("Resource config", this.id, error instanceof Error ? error : new Error(String(error)));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
133
156
|
return {
|
|
134
157
|
[symbolResourceWithConfig]: true,
|
|
135
158
|
id: this.id,
|
|
@@ -164,7 +187,7 @@ export function defineResource<
|
|
|
164
187
|
[symbolFilePath]: filePath,
|
|
165
188
|
},
|
|
166
189
|
},
|
|
167
|
-
meta: constConfig.meta || {},
|
|
190
|
+
meta: (constConfig.meta || {}) as TMeta,
|
|
168
191
|
middleware: constConfig.middleware || [],
|
|
169
192
|
};
|
|
170
193
|
}
|
|
@@ -182,7 +205,7 @@ export function defineIndex<
|
|
|
182
205
|
? T[K]["resource"]
|
|
183
206
|
: T[K];
|
|
184
207
|
} & DependencyMapType
|
|
185
|
-
>(items: T): IResource<void, DependencyValuesType<D
|
|
208
|
+
>(items: T): IResource<void, Promise<DependencyValuesType<D>>, D> {
|
|
186
209
|
// Build dependency map from given items; unwrap `.with()` to the base resource
|
|
187
210
|
const dependencies = {} as D;
|
|
188
211
|
const register: RegisterableItems[] = [];
|
|
@@ -263,6 +286,15 @@ export function defineMiddleware<
|
|
|
263
286
|
return {
|
|
264
287
|
...object,
|
|
265
288
|
with: (config: TConfig) => {
|
|
289
|
+
// Validate config with schema if provided (fail fast)
|
|
290
|
+
if (object.configSchema) {
|
|
291
|
+
try {
|
|
292
|
+
config = object.configSchema.parse(config);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
throw new ValidationError("Middleware config", object.id, error instanceof Error ? error : new Error(String(error)));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
266
298
|
return {
|
|
267
299
|
...object,
|
|
268
300
|
[symbolMiddlewareConfigured]: true,
|
|
@@ -280,7 +312,7 @@ export function defineMiddleware<
|
|
|
280
312
|
[symbolMiddlewareEverywhereTasks]: tasks,
|
|
281
313
|
[symbolMiddlewareEverywhereResources]: resources,
|
|
282
314
|
everywhere() {
|
|
283
|
-
throw
|
|
315
|
+
throw new MiddlewareAlreadyGlobalError(object.id);
|
|
284
316
|
},
|
|
285
317
|
};
|
|
286
318
|
},
|
|
@@ -343,9 +375,9 @@ export function defineOverride(
|
|
|
343
375
|
* - `.with(config)` to create configured instances
|
|
344
376
|
* - `.extract(tags)` to extract this tag from a list of tags
|
|
345
377
|
*/
|
|
346
|
-
export function defineTag<TConfig = void>(
|
|
347
|
-
definition: ITagDefinition<TConfig>
|
|
348
|
-
): ITag<TConfig> {
|
|
378
|
+
export function defineTag<TConfig = void, TEnforceContract = void>(
|
|
379
|
+
definition: ITagDefinition<TConfig, TEnforceContract>
|
|
380
|
+
): ITag<TConfig, TEnforceContract> {
|
|
349
381
|
const id = definition.id;
|
|
350
382
|
|
|
351
383
|
return {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// HasContracts<Meta> → true if contracts present, else false
|
|
2
|
+
|
|
3
|
+
import { ITag, ITagWithConfig } from "./defs";
|
|
4
|
+
import { IMeta } from "./defs";
|
|
5
|
+
|
|
6
|
+
// Keep these param names aligned with your defs.ts: ITag<TConfig, TEnforceContract>
|
|
7
|
+
type NonVoid<T> = [T] extends [void] ? never : T;
|
|
8
|
+
|
|
9
|
+
type ExtractReturnFromTag<T> = T extends ITagWithConfig<any, infer R>
|
|
10
|
+
? NonVoid<R>
|
|
11
|
+
: T extends ITag<any, infer R>
|
|
12
|
+
? NonVoid<R>
|
|
13
|
+
: never;
|
|
14
|
+
|
|
15
|
+
type IsTuple<T extends readonly unknown[]> = number extends T["length"]
|
|
16
|
+
? false
|
|
17
|
+
: true;
|
|
18
|
+
|
|
19
|
+
type FilterContracts<
|
|
20
|
+
TTags extends readonly unknown[],
|
|
21
|
+
Acc extends readonly unknown[] = []
|
|
22
|
+
> = TTags extends readonly [infer H, ...infer R]
|
|
23
|
+
? ExtractReturnFromTag<H> extends never
|
|
24
|
+
? FilterContracts<R, Acc>
|
|
25
|
+
: FilterContracts<R, [...Acc, ExtractReturnFromTag<H>]>
|
|
26
|
+
: Acc;
|
|
27
|
+
|
|
28
|
+
export type ExtractContractsFromTags<TTags extends readonly unknown[]> =
|
|
29
|
+
IsTuple<TTags> extends true
|
|
30
|
+
? FilterContracts<TTags>
|
|
31
|
+
: Array<ExtractReturnFromTag<TTags[number]>>;
|
|
32
|
+
|
|
33
|
+
export type ExtractTagsWithNonVoidReturnTypeFromMeta<TMeta extends IMeta> =
|
|
34
|
+
TMeta extends { tags?: infer TTags }
|
|
35
|
+
? TTags extends readonly unknown[]
|
|
36
|
+
? ExtractContractsFromTags<TTags>
|
|
37
|
+
: []
|
|
38
|
+
: [];
|
|
39
|
+
|
|
40
|
+
type IsNeverTuple<T extends readonly unknown[]> = T extends [] ? true : false;
|
|
41
|
+
|
|
42
|
+
export type HasContracts<T extends IMeta> =
|
|
43
|
+
ExtractTagsWithNonVoidReturnTypeFromMeta<T> extends never[] ? false : true; // HasContracts and enforcement
|
|
44
|
+
|
|
45
|
+
// Ensure a response type satisfies ALL contracts (intersection)
|
|
46
|
+
type UnionToIntersection<U> = (
|
|
47
|
+
U extends any ? (arg: U) => void : never
|
|
48
|
+
) extends (arg: infer I) => void
|
|
49
|
+
? I
|
|
50
|
+
: never;
|
|
51
|
+
|
|
52
|
+
type ContractsUnion<TMeta extends IMeta> =
|
|
53
|
+
ExtractTagsWithNonVoidReturnTypeFromMeta<TMeta> extends readonly (infer U)[]
|
|
54
|
+
? U
|
|
55
|
+
: never;
|
|
56
|
+
|
|
57
|
+
type ContractsIntersection<TMeta extends IMeta> = UnionToIntersection<
|
|
58
|
+
ContractsUnion<TMeta>
|
|
59
|
+
>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Pretty-print helper to expand intersections for better IDE display.
|
|
63
|
+
*/
|
|
64
|
+
type Simplify<T> = { [K in keyof T]: T[K] } & {};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Verbose compile-time error surfaced when a value does not satisfy
|
|
68
|
+
* the intersection of all tag-enforced contracts.
|
|
69
|
+
*
|
|
70
|
+
* Intersected with `never` in call sites when desired to ensure assignment
|
|
71
|
+
* fails while still surfacing a readable shape in tooltips.
|
|
72
|
+
*/
|
|
73
|
+
export type ContractViolationError<TMeta extends IMeta, TActual> = {
|
|
74
|
+
message: "Value does not satisfy all tag contracts";
|
|
75
|
+
expected: Simplify<ContractsIntersection<TMeta>>;
|
|
76
|
+
received: TActual;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type EnsureResponseSatisfiesContracts<TMeta extends IMeta, TResponse> = [
|
|
80
|
+
ContractsUnion<TMeta>
|
|
81
|
+
] extends [never]
|
|
82
|
+
? TResponse // no contracts, allow as-is
|
|
83
|
+
: TResponse extends Promise<infer U>
|
|
84
|
+
? Promise<
|
|
85
|
+
U extends ContractsIntersection<TMeta>
|
|
86
|
+
? U
|
|
87
|
+
: ContractViolationError<TMeta, U>
|
|
88
|
+
>
|
|
89
|
+
: TResponse extends ContractsIntersection<TMeta>
|
|
90
|
+
? TResponse
|
|
91
|
+
: ContractViolationError<TMeta, TResponse>;
|
package/src/defs.ts
CHANGED
|
@@ -15,6 +15,23 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { MiddlewareEverywhereOptions } from "./define";
|
|
18
|
+
import {
|
|
19
|
+
EnsureResponseSatisfiesContracts,
|
|
20
|
+
HasContracts,
|
|
21
|
+
} from "./defs.returnTag";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generic validation schema interface that can be implemented by any validation library.
|
|
25
|
+
* Compatible with Zod, Yup, Joi, and other validation libraries.
|
|
26
|
+
*/
|
|
27
|
+
export interface IValidationSchema<T = any> {
|
|
28
|
+
/**
|
|
29
|
+
* Parse and validate the input data.
|
|
30
|
+
* Should throw an error if validation fails.
|
|
31
|
+
* Can transform the data if the schema supports transformations.
|
|
32
|
+
*/
|
|
33
|
+
parse(input: unknown): T;
|
|
34
|
+
}
|
|
18
35
|
|
|
19
36
|
// Re-export public cache type so consumers don’t import from internals.
|
|
20
37
|
export { ICacheInstance } from "./globals/middleware/cache.middleware";
|
|
@@ -57,17 +74,17 @@ export const symbolIndexResource: unique symbol = Symbol(
|
|
|
57
74
|
"runner.indexResource"
|
|
58
75
|
);
|
|
59
76
|
|
|
60
|
-
export interface ITagDefinition<TConfig = void> {
|
|
77
|
+
export interface ITagDefinition<TConfig = void, TEnforceContract = void> {
|
|
61
78
|
id: string | symbol;
|
|
62
79
|
}
|
|
63
80
|
|
|
64
81
|
/**
|
|
65
82
|
* A configured instance of a tag as produced by `ITag.with()`.
|
|
66
83
|
*/
|
|
67
|
-
export interface ITagWithConfig<TConfig = void> {
|
|
84
|
+
export interface ITagWithConfig<TConfig = void, TEnforceContract = void> {
|
|
68
85
|
id: string | symbol;
|
|
69
86
|
/** The tag definition used to produce this configured instance. */
|
|
70
|
-
tag: ITag<TConfig>;
|
|
87
|
+
tag: ITag<TConfig, TEnforceContract>;
|
|
71
88
|
/** The configuration captured for this tag instance. */
|
|
72
89
|
config: TConfig;
|
|
73
90
|
}
|
|
@@ -76,16 +93,19 @@ export interface ITagWithConfig<TConfig = void> {
|
|
|
76
93
|
* A tag definition (builder). Use `.with(config)` to obtain configured instances,
|
|
77
94
|
* and `.extract(tags)` to find either a configured instance or the bare tag in a list.
|
|
78
95
|
*/
|
|
79
|
-
export interface ITag<TConfig = void
|
|
96
|
+
export interface ITag<TConfig = void, TEnforceContract = void>
|
|
97
|
+
extends ITagDefinition<TConfig, TEnforceContract> {
|
|
80
98
|
/**
|
|
81
99
|
* Creates a configured instance of the tag.
|
|
82
100
|
*/
|
|
83
|
-
with(config: TConfig): ITagWithConfig<TConfig>;
|
|
101
|
+
with(config: TConfig): ITagWithConfig<TConfig, TEnforceContract>;
|
|
84
102
|
/**
|
|
85
103
|
* Extracts either a configured instance or the bare tag from a list of tags
|
|
86
104
|
* or from a taggable object (`{ meta: { tags?: [] } }`).
|
|
87
105
|
*/
|
|
88
|
-
extract(
|
|
106
|
+
extract(
|
|
107
|
+
target: TagType[] | ITaggable
|
|
108
|
+
): ExtractedTagResult<TConfig, TEnforceContract> | null;
|
|
89
109
|
[symbolFilePath]: string;
|
|
90
110
|
}
|
|
91
111
|
|
|
@@ -96,9 +116,9 @@ export interface ITag<TConfig = void> extends ITagDefinition<TConfig> {
|
|
|
96
116
|
*/
|
|
97
117
|
export type TagType =
|
|
98
118
|
| string
|
|
99
|
-
| ITag<void>
|
|
100
|
-
| ITag<{ [K in any]?: any }>
|
|
101
|
-
| ITagWithConfig<any>;
|
|
119
|
+
| ITag<void, any>
|
|
120
|
+
| ITag<{ [K in any]?: any }, any>
|
|
121
|
+
| ITagWithConfig<any, any>;
|
|
102
122
|
|
|
103
123
|
/**
|
|
104
124
|
* Conditional result type for `ITag.extract`:
|
|
@@ -106,7 +126,7 @@ export type TagType =
|
|
|
106
126
|
* - For optional object config → identifier with optional config
|
|
107
127
|
* - For required config → identifier with required config
|
|
108
128
|
*/
|
|
109
|
-
export type ExtractedTagResult<TConfig> = {} extends TConfig
|
|
129
|
+
export type ExtractedTagResult<TConfig, TEnforceContract> = {} extends TConfig
|
|
110
130
|
? { id: string | symbol; config?: TConfig }
|
|
111
131
|
: { id: string | symbol; config: TConfig };
|
|
112
132
|
|
|
@@ -119,7 +139,6 @@ export interface ITaggable {
|
|
|
119
139
|
tags?: TagType[];
|
|
120
140
|
};
|
|
121
141
|
}
|
|
122
|
-
|
|
123
142
|
/**
|
|
124
143
|
* Common metadata you can attach to tasks/resources/events/middleware.
|
|
125
144
|
* Useful for docs, filtering and middleware decisions.
|
|
@@ -149,7 +168,9 @@ export type DependencyMapType = Record<
|
|
|
149
168
|
type ExtractTaskInput<T> = T extends ITask<infer I, any, infer D> ? I : never;
|
|
150
169
|
type ExtractTaskOutput<T> = T extends ITask<any, infer O, infer D> ? O : never;
|
|
151
170
|
type ExtractResourceValue<T> = T extends IResource<any, infer V, infer D>
|
|
152
|
-
? V
|
|
171
|
+
? V extends Promise<infer U>
|
|
172
|
+
? U
|
|
173
|
+
: V
|
|
153
174
|
: never;
|
|
154
175
|
|
|
155
176
|
type ExtractEventParams<T> = T extends IEvent<infer P> ? P : never;
|
|
@@ -213,7 +234,8 @@ export interface ITaskDefinition<
|
|
|
213
234
|
TInput = any,
|
|
214
235
|
TOutput extends Promise<any> = any,
|
|
215
236
|
TDependencies extends DependencyMapType = {},
|
|
216
|
-
TOn extends "*" | IEventDefinition<any> | undefined = undefined // Adding a generic to track 'on' type
|
|
237
|
+
TOn extends "*" | IEventDefinition<any> | undefined = undefined, // Adding a generic to track 'on' type,
|
|
238
|
+
TMeta extends ITaskMeta = any
|
|
217
239
|
> {
|
|
218
240
|
/**
|
|
219
241
|
* Stable identifier. If omitted, an anonymous id is generated from file path
|
|
@@ -237,7 +259,12 @@ export interface ITaskDefinition<
|
|
|
237
259
|
*/
|
|
238
260
|
listenerOrder?: number;
|
|
239
261
|
/** Optional metadata used for docs, filtering and tooling. */
|
|
240
|
-
meta?:
|
|
262
|
+
meta?: TMeta;
|
|
263
|
+
/**
|
|
264
|
+
* Optional validation schema for runtime input validation.
|
|
265
|
+
* When provided, task input will be validated before execution.
|
|
266
|
+
*/
|
|
267
|
+
inputSchema?: IValidationSchema<TInput>;
|
|
241
268
|
/**
|
|
242
269
|
* The task body. If `on` is set, the input is an `IEventEmission`. Otherwise,
|
|
243
270
|
* it's the declared input type.
|
|
@@ -247,7 +274,9 @@ export interface ITaskDefinition<
|
|
|
247
274
|
? TInput
|
|
248
275
|
: IEventEmission<TOn extends "*" ? any : ExtractEventParams<TOn>>,
|
|
249
276
|
dependencies: DependencyValuesType<TDependencies>
|
|
250
|
-
) =>
|
|
277
|
+
) => HasContracts<TMeta> extends true
|
|
278
|
+
? EnsureResponseSatisfiesContracts<TMeta, TOutput>
|
|
279
|
+
: TOutput;
|
|
251
280
|
}
|
|
252
281
|
|
|
253
282
|
export type BeforeRunEventPayload<TInput> = {
|
|
@@ -284,8 +313,9 @@ export interface ITask<
|
|
|
284
313
|
TInput = any,
|
|
285
314
|
TOutput extends Promise<any> = any,
|
|
286
315
|
TDependencies extends DependencyMapType = {},
|
|
287
|
-
TOn extends "*" | IEventDefinition<any> | undefined = undefined
|
|
288
|
-
|
|
316
|
+
TOn extends "*" | IEventDefinition<any> | undefined = undefined,
|
|
317
|
+
TMeta extends ITaskMeta = any
|
|
318
|
+
> extends ITaskDefinition<TInput, TOutput, TDependencies, TOn, TMeta> {
|
|
289
319
|
id: string | symbol;
|
|
290
320
|
dependencies: TDependencies | (() => TDependencies);
|
|
291
321
|
computedDependencies?: DependencyValuesType<TDependencies>;
|
|
@@ -304,11 +334,12 @@ export interface ITask<
|
|
|
304
334
|
|
|
305
335
|
export interface IResourceDefinition<
|
|
306
336
|
TConfig = any,
|
|
307
|
-
TValue =
|
|
337
|
+
TValue extends Promise<any> = Promise<any>,
|
|
308
338
|
TDependencies extends DependencyMapType = {},
|
|
309
339
|
TContext = any,
|
|
310
340
|
THooks = any,
|
|
311
|
-
TRegisterableItems = any
|
|
341
|
+
TRegisterableItems = any,
|
|
342
|
+
TMeta extends IResourceMeta = any
|
|
312
343
|
> {
|
|
313
344
|
/** Stable identifier. Omit to get an anonymous id. */
|
|
314
345
|
id?: string | symbol;
|
|
@@ -329,7 +360,9 @@ export interface IResourceDefinition<
|
|
|
329
360
|
config: TConfig,
|
|
330
361
|
dependencies: DependencyValuesType<TDependencies>,
|
|
331
362
|
context: TContext
|
|
332
|
-
) =>
|
|
363
|
+
) => HasContracts<TMeta> extends true
|
|
364
|
+
? EnsureResponseSatisfiesContracts<TMeta, TValue>
|
|
365
|
+
: TValue;
|
|
333
366
|
/**
|
|
334
367
|
* Clean-up function for the resource. This is called when the resource is no longer needed.
|
|
335
368
|
*
|
|
@@ -340,12 +373,17 @@ export interface IResourceDefinition<
|
|
|
340
373
|
*/
|
|
341
374
|
dispose?: (
|
|
342
375
|
this: any,
|
|
343
|
-
value: TValue,
|
|
376
|
+
value: TValue extends Promise<infer U> ? U : TValue,
|
|
344
377
|
config: TConfig,
|
|
345
378
|
dependencies: DependencyValuesType<TDependencies>,
|
|
346
379
|
context: TContext
|
|
347
380
|
) => Promise<void>;
|
|
348
|
-
meta?:
|
|
381
|
+
meta?: TMeta;
|
|
382
|
+
/**
|
|
383
|
+
* Optional validation schema for runtime config validation.
|
|
384
|
+
* When provided, resource config will be validated when .with() is called.
|
|
385
|
+
*/
|
|
386
|
+
configSchema?: IValidationSchema<TConfig>;
|
|
349
387
|
/**
|
|
350
388
|
* Safe overrides to swap behavior while preserving identities. See
|
|
351
389
|
* README: Overrides.
|
|
@@ -370,10 +408,19 @@ export interface IResourceDefinition<
|
|
|
370
408
|
|
|
371
409
|
export interface IResource<
|
|
372
410
|
TConfig = void,
|
|
373
|
-
TValue = any
|
|
411
|
+
TValue extends Promise<any> = Promise<any>,
|
|
374
412
|
TDependencies extends DependencyMapType = any,
|
|
375
|
-
TContext = any
|
|
376
|
-
|
|
413
|
+
TContext = any,
|
|
414
|
+
TMeta extends IResourceMeta = any
|
|
415
|
+
> extends IResourceDefinition<
|
|
416
|
+
TConfig,
|
|
417
|
+
TValue,
|
|
418
|
+
TDependencies,
|
|
419
|
+
TContext,
|
|
420
|
+
any,
|
|
421
|
+
any,
|
|
422
|
+
TMeta
|
|
423
|
+
> {
|
|
377
424
|
id: string | symbol;
|
|
378
425
|
with(config: TConfig): IResourceWithConfig<TConfig, TValue, TDependencies>;
|
|
379
426
|
register:
|
|
@@ -396,7 +443,7 @@ export interface IResource<
|
|
|
396
443
|
|
|
397
444
|
export interface IResourceWithConfig<
|
|
398
445
|
TConfig = any,
|
|
399
|
-
TValue = any
|
|
446
|
+
TValue extends Promise<any> = Promise<any>,
|
|
400
447
|
TDependencies extends DependencyMapType = any
|
|
401
448
|
> {
|
|
402
449
|
/** The id of the underlying resource. */
|
|
@@ -415,6 +462,11 @@ export interface IEventDefinition<TPayload = void> {
|
|
|
415
462
|
/** Stable identifier. Omit to get an anonymous id. */
|
|
416
463
|
id?: string | symbol;
|
|
417
464
|
meta?: IEventMeta;
|
|
465
|
+
/**
|
|
466
|
+
* Optional validation schema for runtime payload validation.
|
|
467
|
+
* When provided, event payload will be validated when emitted.
|
|
468
|
+
*/
|
|
469
|
+
payloadSchema?: IValidationSchema<TPayload>;
|
|
418
470
|
}
|
|
419
471
|
|
|
420
472
|
export interface IEvent<TPayload = any> extends IEventDefinition<TPayload> {
|
|
@@ -469,6 +521,11 @@ export interface IMiddlewareDefinition<
|
|
|
469
521
|
id?: string | symbol;
|
|
470
522
|
/** Static or lazy dependency map. */
|
|
471
523
|
dependencies?: TDependencies | ((config: TConfig) => TDependencies);
|
|
524
|
+
/**
|
|
525
|
+
* Optional validation schema for runtime config validation.
|
|
526
|
+
* When provided, middleware config will be validated when .with() is called.
|
|
527
|
+
*/
|
|
528
|
+
configSchema?: IValidationSchema<TConfig>;
|
|
472
529
|
/**
|
|
473
530
|
* The middleware body, called with task/resource execution input.
|
|
474
531
|
*/
|
|
@@ -530,7 +587,7 @@ export interface IMiddlewareExecutionInput<
|
|
|
530
587
|
};
|
|
531
588
|
/** Resource hook: present when wrapping init/dispose. */
|
|
532
589
|
resource?: {
|
|
533
|
-
definition: IResource<TResourceConfig>;
|
|
590
|
+
definition: IResource<TResourceConfig, any, any, any, any>;
|
|
534
591
|
config: TResourceConfig;
|
|
535
592
|
};
|
|
536
593
|
next: (
|
package/src/errors.ts
CHANGED
|
@@ -1,33 +1,105 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all BlueLibs Runner errors
|
|
3
|
+
*/
|
|
4
|
+
export class RuntimeError extends Error {
|
|
5
|
+
constructor(message: string) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "RuntimeError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
2
10
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown when attempting to register a component with a duplicate ID
|
|
13
|
+
*/
|
|
14
|
+
export class DuplicateRegistrationError extends RuntimeError {
|
|
15
|
+
constructor(type: string, id: string | symbol) {
|
|
16
|
+
super(`${type} "${id.toString()}" already registered`);
|
|
17
|
+
this.name = "DuplicateRegistrationError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
6
20
|
|
|
7
|
-
|
|
8
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when a dependency is not found in the registry
|
|
23
|
+
*/
|
|
24
|
+
export class DependencyNotFoundError extends RuntimeError {
|
|
25
|
+
constructor(key: string | symbol) {
|
|
26
|
+
super(
|
|
9
27
|
`Dependency ${key.toString()} not found. Did you forget to register it through a resource?`
|
|
10
|
-
)
|
|
28
|
+
);
|
|
29
|
+
this.name = "DependencyNotFoundError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
11
32
|
|
|
12
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Error thrown when an unknown item type is encountered
|
|
35
|
+
*/
|
|
36
|
+
export class UnknownItemTypeError extends RuntimeError {
|
|
37
|
+
constructor(item: any) {
|
|
38
|
+
super(`Unknown item type: ${item}`);
|
|
39
|
+
this.name = "UnknownItemTypeError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
13
42
|
|
|
14
|
-
|
|
15
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Error thrown when circular dependencies are detected
|
|
45
|
+
*/
|
|
46
|
+
export class CircularDependenciesError extends RuntimeError {
|
|
47
|
+
constructor(cycles: string[]) {
|
|
48
|
+
super(`Circular dependencies detected: ${cycles.join(", ")}`);
|
|
49
|
+
this.name = "CircularDependenciesError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
16
52
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Error thrown when an event is not found in the registry
|
|
55
|
+
*/
|
|
56
|
+
export class EventNotFoundError extends RuntimeError {
|
|
57
|
+
constructor(id: string | symbol) {
|
|
58
|
+
super(`Event "${id.toString()}" not found. Did you forget to register it?`);
|
|
59
|
+
this.name = "EventNotFoundError";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
21
62
|
|
|
22
|
-
|
|
23
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Error thrown when attempting to make a middleware global when it's already global
|
|
65
|
+
*/
|
|
66
|
+
export class MiddlewareAlreadyGlobalError extends RuntimeError {
|
|
67
|
+
constructor(id: string | symbol) {
|
|
68
|
+
super(
|
|
24
69
|
"Cannot call .everywhere() on an already global middleware: " +
|
|
25
|
-
id.toString
|
|
26
|
-
)
|
|
70
|
+
id.toString()
|
|
71
|
+
);
|
|
72
|
+
this.name = "MiddlewareAlreadyGlobalError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
27
75
|
|
|
28
|
-
|
|
29
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Error thrown when attempting to modify a locked component
|
|
78
|
+
*/
|
|
79
|
+
export class LockedError extends RuntimeError {
|
|
80
|
+
constructor(what: string | symbol) {
|
|
81
|
+
super(`Cannot modify the ${what.toString()} when it is locked.`);
|
|
82
|
+
this.name = "LockedError";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
30
85
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Error thrown when attempting to initialize a store that's already initialized
|
|
88
|
+
*/
|
|
89
|
+
export class StoreAlreadyInitializedError extends RuntimeError {
|
|
90
|
+
constructor() {
|
|
91
|
+
super("Store already initialized. Cannot reinitialize.");
|
|
92
|
+
this.name = "StoreAlreadyInitializedError";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Error thrown when validation fails for task input, resource config, middleware config, or event payload
|
|
98
|
+
*/
|
|
99
|
+
export class ValidationError extends RuntimeError {
|
|
100
|
+
constructor(type: string, id: string | symbol, originalError: Error | string) {
|
|
101
|
+
const errorMessage = originalError instanceof Error ? originalError.message : String(originalError);
|
|
102
|
+
super(`${type} validation failed for ${id.toString()}: ${errorMessage}`);
|
|
103
|
+
this.name = "ValidationError";
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,11 @@ import * as utils from "../define";
|
|
|
13
13
|
import { EventManager } from "./EventManager";
|
|
14
14
|
import { ResourceInitializer } from "./ResourceInitializer";
|
|
15
15
|
import { TaskRunner } from "./TaskRunner";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
DependencyNotFoundError,
|
|
18
|
+
EventNotFoundError,
|
|
19
|
+
UnknownItemTypeError,
|
|
20
|
+
} from "../errors";
|
|
17
21
|
import { Logger } from "./Logger";
|
|
18
22
|
|
|
19
23
|
/**
|
|
@@ -148,7 +152,7 @@ export class DependencyProcessor {
|
|
|
148
152
|
});
|
|
149
153
|
} else {
|
|
150
154
|
if (this.store.events.get(eventDefinition.id) === undefined) {
|
|
151
|
-
throw
|
|
155
|
+
throw new EventNotFoundError(eventDefinition.id);
|
|
152
156
|
}
|
|
153
157
|
this.eventManager.addListener(eventDefinition, handler, {
|
|
154
158
|
order: task.task.listenerOrder || 0,
|
|
@@ -179,7 +183,7 @@ export class DependencyProcessor {
|
|
|
179
183
|
} else if (utils.isEvent(object)) {
|
|
180
184
|
return this.extractEventDependency(object, source);
|
|
181
185
|
} else {
|
|
182
|
-
throw
|
|
186
|
+
throw new UnknownItemTypeError(object);
|
|
183
187
|
}
|
|
184
188
|
}
|
|
185
189
|
|
|
@@ -197,7 +201,7 @@ export class DependencyProcessor {
|
|
|
197
201
|
async extractTaskDependency(object: ITask<any, any, {}>) {
|
|
198
202
|
const storeTask = this.store.tasks.get(object.id);
|
|
199
203
|
if (storeTask === undefined) {
|
|
200
|
-
throw
|
|
204
|
+
throw new DependencyNotFoundError(`Task ${object.id.toString()}`);
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
if (!storeTask.isInitialized) {
|
|
@@ -221,7 +225,7 @@ export class DependencyProcessor {
|
|
|
221
225
|
// check if it exists in the store with the value
|
|
222
226
|
const storeResource = this.store.resources.get(object.id);
|
|
223
227
|
if (storeResource === undefined) {
|
|
224
|
-
throw
|
|
228
|
+
throw new DependencyNotFoundError(`Resource ${object.id.toString()}`);
|
|
225
229
|
}
|
|
226
230
|
|
|
227
231
|
const { resource, config } = storeResource;
|