@bluelibs/runner 3.3.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +437 -33
  2. package/dist/define.d.ts +5 -5
  3. package/dist/define.js +22 -2
  4. package/dist/define.js.map +1 -1
  5. package/dist/defs.d.ts +55 -21
  6. package/dist/defs.js.map +1 -1
  7. package/dist/defs.returnTag.d.ts +36 -0
  8. package/dist/defs.returnTag.js +4 -0
  9. package/dist/defs.returnTag.js.map +1 -0
  10. package/dist/errors.d.ts +60 -10
  11. package/dist/errors.js +103 -12
  12. package/dist/errors.js.map +1 -1
  13. package/dist/globals/globalMiddleware.d.ts +4 -4
  14. package/dist/globals/globalResources.d.ts +28 -10
  15. package/dist/globals/middleware/cache.middleware.d.ts +9 -9
  16. package/dist/globals/resources/queue.resource.d.ts +5 -2
  17. package/dist/index.d.ts +33 -14
  18. package/dist/index.js +2 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/models/DependencyProcessor.js +4 -4
  21. package/dist/models/DependencyProcessor.js.map +1 -1
  22. package/dist/models/EventManager.js +10 -1
  23. package/dist/models/EventManager.js.map +1 -1
  24. package/dist/models/Logger.d.ts +8 -0
  25. package/dist/models/Logger.js +24 -0
  26. package/dist/models/Logger.js.map +1 -1
  27. package/dist/models/OverrideManager.js +1 -1
  28. package/dist/models/OverrideManager.js.map +1 -1
  29. package/dist/models/ResourceInitializer.d.ts +2 -2
  30. package/dist/models/ResourceInitializer.js.map +1 -1
  31. package/dist/models/Store.d.ts +2 -2
  32. package/dist/models/Store.js +1 -1
  33. package/dist/models/Store.js.map +1 -1
  34. package/dist/models/StoreConstants.d.ts +6 -3
  35. package/dist/models/StoreRegistry.d.ts +2 -2
  36. package/dist/models/StoreRegistry.js +1 -1
  37. package/dist/models/StoreRegistry.js.map +1 -1
  38. package/dist/models/StoreTypes.d.ts +1 -1
  39. package/dist/models/StoreValidator.js +5 -5
  40. package/dist/models/StoreValidator.js.map +1 -1
  41. package/dist/models/TaskRunner.js +10 -0
  42. package/dist/models/TaskRunner.js.map +1 -1
  43. package/dist/run.d.ts +3 -3
  44. package/dist/run.js +1 -1
  45. package/dist/run.js.map +1 -1
  46. package/dist/t1.d.ts +1 -0
  47. package/dist/t1.js +13 -0
  48. package/dist/t1.js.map +1 -0
  49. package/dist/testing.d.ts +1 -1
  50. package/package.json +2 -2
  51. package/src/__tests__/errors.test.ts +92 -11
  52. package/src/__tests__/models/EventManager.test.ts +0 -1
  53. package/src/__tests__/models/Logger.test.ts +82 -5
  54. package/src/__tests__/recursion/c.resource.ts +1 -1
  55. package/src/__tests__/run.overrides.test.ts +3 -3
  56. package/src/__tests__/typesafety.test.ts +112 -9
  57. package/src/__tests__/validation-edge-cases.test.ts +111 -0
  58. package/src/__tests__/validation-interface.test.ts +428 -0
  59. package/src/define.ts +47 -15
  60. package/src/defs.returnTag.ts +91 -0
  61. package/src/defs.ts +84 -27
  62. package/src/errors.ts +95 -23
  63. package/src/index.ts +1 -0
  64. package/src/models/DependencyProcessor.ts +9 -5
  65. package/src/models/EventManager.ts +12 -3
  66. package/src/models/Logger.ts +28 -0
  67. package/src/models/OverrideManager.ts +2 -7
  68. package/src/models/ResourceInitializer.ts +8 -3
  69. package/src/models/Store.ts +3 -3
  70. package/src/models/StoreRegistry.ts +2 -2
  71. package/src/models/StoreTypes.ts +1 -1
  72. package/src/models/StoreValidator.ts +6 -6
  73. package/src/models/TaskRunner.ts +10 -1
  74. package/src/run.ts +8 -5
  75. package/src/testing.ts +1 -1
package/dist/run.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":";;AAsDA,kBA0DC;AAhHD,oDAAiD;AAUjD,sEAAmE;AACnE,wDAAqD;AACrD,yDAAsD;AACtD,0CAAuC;AACvC,+EAA4E;AAC5E,qCAAkC;AAClC,+DAA4D;AAC5D,4CAAyC;AAqClC,KAAK,UAAU,GAAG,CACvB,QAAyB,EACzB,MAAU;IAEV,MAAM,YAAY,GAAG,IAAI,2BAAY,EAAE,CAAC;IAExC,2FAA2F;IAC3F,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,YAAY,CAAC,CAAC;IAExC,MAAM,KAAK,GAAG,IAAI,aAAK,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,IAAI,uBAAU,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,yCAAmB,CACvC,KAAK,EACL,YAAY,EACZ,UAAU,EACV,MAAM,CACP,CAAC;IAEF,+FAA+F;IAC/F,KAAK,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,KAAK,CAAC,gBAAgB,CAAC,iCAAe,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,KAAK,CAAC,gBAAgB,CAAC,iCAAe,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAEpE,kGAAkG;IAClG,MAAM,cAAc,GAAG,KAAK,CAAC,iBAAiB,EAAE,CAAC;IACjD,MAAM,oBAAoB,GAAG,IAAA,mDAAwB,EAAC,cAAc,CAAC,CAAC;IACtE,IAAI,oBAAoB,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,eAAM,CAAC,oBAAoB,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACjE,CAAC;IAED,sFAAsF;IACtF,MAAM,KAAK,CAAC,gBAAgB,EAAE,CAAC;IAE/B,iGAAiG;IACjG,MAAM,KAAK,CAAC,sBAAsB,EAAE,CAAC;IACrC,MAAM,SAAS,CAAC,eAAe,EAAE,CAAC;IAClC,MAAM,SAAS,CAAC,sBAAsB,EAAE,CAAC;IAEzC,6DAA6D;IAC7D,MAAM,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAE1D,mFAAmF;IACnF,iCAAiC;IACjC,MAAM,YAAY,CAAC,IAAI,CAAC,2BAAY,CAAC,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEpE,0CAA0C;IAC1C,MAAM,SAAS,CAAC,cAAc,EAAE,CAAC;IAEjC,MAAM,YAAY,CAAC,IAAI,CAAC,2BAAY,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;IACnE,MAAM,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAE1D,0CAA0C;IAC1C,KAAK,CAAC,IAAI,EAAE,CAAC;IAEb,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK;QACvB,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE;KAC/B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":";;AAsDA,kBA6DC;AAnHD,oDAAiD;AAUjD,sEAAmE;AACnE,wDAAqD;AACrD,yDAAsD;AACtD,0CAAuC;AACvC,+EAA4E;AAC5E,qCAAqD;AACrD,+DAA4D;AAC5D,4CAAyC;AAqClC,KAAK,UAAU,GAAG,CACvB,QAAiE,EACjE,MAAU;IAKV,MAAM,YAAY,GAAG,IAAI,2BAAY,EAAE,CAAC;IAExC,2FAA2F;IAC3F,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,YAAY,CAAC,CAAC;IAExC,MAAM,KAAK,GAAG,IAAI,aAAK,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,IAAI,uBAAU,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,yCAAmB,CACvC,KAAK,EACL,YAAY,EACZ,UAAU,EACV,MAAM,CACP,CAAC;IAEF,+FAA+F;IAC/F,KAAK,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,KAAK,CAAC,gBAAgB,CAAC,iCAAe,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5D,KAAK,CAAC,gBAAgB,CAAC,iCAAe,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAEpE,kGAAkG;IAClG,MAAM,cAAc,GAAG,KAAK,CAAC,iBAAiB,EAAE,CAAC;IACjD,MAAM,oBAAoB,GAAG,IAAA,mDAAwB,EAAC,cAAc,CAAC,CAAC;IACtE,IAAI,oBAAoB,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,kCAAyB,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACnE,CAAC;IAED,sFAAsF;IACtF,MAAM,KAAK,CAAC,gBAAgB,EAAE,CAAC;IAE/B,iGAAiG;IACjG,MAAM,KAAK,CAAC,sBAAsB,EAAE,CAAC;IACrC,MAAM,SAAS,CAAC,eAAe,EAAE,CAAC;IAClC,MAAM,SAAS,CAAC,sBAAsB,EAAE,CAAC;IAEzC,6DAA6D;IAC7D,MAAM,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAE1D,mFAAmF;IACnF,iCAAiC;IACjC,MAAM,YAAY,CAAC,IAAI,CAAC,2BAAY,CAAC,UAAU,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEpE,0CAA0C;IAC1C,MAAM,SAAS,CAAC,cAAc,EAAE,CAAC;IAEjC,MAAM,YAAY,CAAC,IAAI,CAAC,2BAAY,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;IACnE,MAAM,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAE1D,0CAA0C;IAC1C,KAAK,CAAC,IAAI,EAAE,CAAC;IAEb,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK;QACvB,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE;KAC/B,CAAC;AACJ,CAAC"}
package/dist/t1.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/t1.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const define_1 = require("./define");
4
+ const tag = (0, define_1.defineTag)({ id: "usertag" });
5
+ const tag2 = (0, define_1.defineTag)({ id: "usertag2" });
6
+ const tag3 = (0, define_1.defineTag)({ id: "usertag3" });
7
+ // Preserve as a const tuple for type extraction
8
+ const tags = [tag.with({ value: 123 }), tag2];
9
+ // Build runtime meta by spreading the tuple (meta.tags becomes an array)
10
+ const meta = {
11
+ tags: ["string", tag2, tag3, tag.with({ value: 123 })],
12
+ };
13
+ //# sourceMappingURL=t1.js.map
package/dist/t1.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"t1.js","sourceRoot":"","sources":["../src/t1.ts"],"names":[],"mappings":";;AAAA,qCAAqC;AAgBrC,MAAM,GAAG,GAAG,IAAA,kBAAS,EAA2B,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;AACnE,MAAM,IAAI,GAAG,IAAA,kBAAS,EAAe,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;AACzD,MAAM,IAAI,GAAG,IAAA,kBAAS,EAAa,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;AAEvD,gDAAgD;AAChD,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,CAAU,CAAC;AAEvD,yEAAyE;AACzE,MAAM,IAAI,GAAG;IACX,IAAI,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;CACvC,CAAC"}
package/dist/testing.d.ts CHANGED
@@ -7,7 +7,7 @@ import { EventManager, Logger, Store, TaskRunner } from "./models";
7
7
  */
8
8
  export declare function createTestResource(root: RegisterableItems, options?: {
9
9
  overrides?: Array<IResource | ITask | IMiddleware | IResourceWithConfig>;
10
- }): IResource<void, ReturnType<typeof buildTestFacade>>;
10
+ }): IResource<void, Promise<ReturnType<typeof buildTestFacade>>>;
11
11
  declare function buildTestFacade(deps: {
12
12
  taskRunner: TaskRunner;
13
13
  store: Store;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluelibs/runner",
3
- "version": "3.3.2",
3
+ "version": "3.4.0",
4
4
  "description": "BlueLibs Runner",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -15,7 +15,7 @@
15
15
  "coverage": "jest --verbose --coverage",
16
16
  "test:clean": "jest --clearCache",
17
17
  "testonly": "npm test",
18
- "test:ci": "npm run coverage -- --ci --maxWorkers=2 --reporters=default --reporters=jest-junit",
18
+ "test:ci": "jest --verbose --coverage --ci --maxWorkers=2 --reporters=default --reporters=jest-junit",
19
19
  "prepublishOnly": "npm run build",
20
20
  "typedoc": "typedoc --options typedoc.json",
21
21
  "benchmark": "jest --testMatch=\"**/__tests__/benchmark/benchmark.test.ts\" --testTimeout 10000"
@@ -5,7 +5,20 @@ import {
5
5
  defineMiddleware,
6
6
  } from "../define";
7
7
  import { run } from "../run";
8
- import { Errors } from "../errors";
8
+ import { Errors } from "..";
9
+
10
+ const {
11
+ RuntimeError,
12
+ DuplicateRegistrationError,
13
+ DependencyNotFoundError,
14
+ UnknownItemTypeError,
15
+ CircularDependenciesError,
16
+ EventNotFoundError,
17
+ MiddlewareAlreadyGlobalError,
18
+ LockedError,
19
+ StoreAlreadyInitializedError,
20
+ ValidationError,
21
+ } = Errors;
9
22
 
10
23
  describe("Errors", () => {
11
24
  it("should throw duplicateRegistration error", async () => {
@@ -18,7 +31,7 @@ describe("Errors", () => {
18
31
  });
19
32
 
20
33
  await expect(run(app)).rejects.toThrow(
21
- Errors.duplicateRegistration("Task", "test.task").message
34
+ new DuplicateRegistrationError("Task", "test.task").message
22
35
  );
23
36
  });
24
37
 
@@ -38,7 +51,9 @@ describe("Errors", () => {
38
51
  },
39
52
  });
40
53
 
41
- await expect(run(app)).rejects.toThrow(Errors.unknownItemType({}).message);
54
+ await expect(run(app)).rejects.toThrow(
55
+ new UnknownItemTypeError({}).message
56
+ );
42
57
  });
43
58
 
44
59
  it("should throw unknown item type error at resource level", async () => {
@@ -52,7 +67,9 @@ describe("Errors", () => {
52
67
  async init(_, {}) {},
53
68
  });
54
69
 
55
- await expect(run(app)).rejects.toThrow(Errors.unknownItemType({}).message);
70
+ await expect(run(app)).rejects.toThrow(
71
+ new UnknownItemTypeError({}).message
72
+ );
56
73
  });
57
74
 
58
75
  it("should throw circularDependencies error", async () => {
@@ -91,7 +108,7 @@ describe("Errors", () => {
91
108
  });
92
109
 
93
110
  await expect(run(app)).rejects.toThrow(
94
- Errors.eventNotFound("non.existent.event").message
111
+ new EventNotFoundError("non.existent.event").message
95
112
  );
96
113
  });
97
114
 
@@ -148,7 +165,7 @@ describe("Errors", () => {
148
165
  });
149
166
 
150
167
  await expect(run(app)).rejects.toThrow(
151
- Errors.duplicateRegistration("Resource", "res1").message
168
+ new DuplicateRegistrationError("Resource", "res1").message
152
169
  );
153
170
  });
154
171
 
@@ -169,7 +186,7 @@ describe("Errors", () => {
169
186
  });
170
187
 
171
188
  await expect(run(app)).rejects.toThrow(
172
- Errors.duplicateRegistration("Middleware", "middlewarex").message
189
+ new DuplicateRegistrationError("Middleware", "middlewarex").message
173
190
  );
174
191
  });
175
192
 
@@ -187,7 +204,7 @@ describe("Errors", () => {
187
204
  });
188
205
 
189
206
  await expect(run(app)).rejects.toThrow(
190
- Errors.duplicateRegistration("Event", "ev1").message
207
+ new DuplicateRegistrationError("Event", "ev1").message
191
208
  );
192
209
  });
193
210
 
@@ -216,7 +233,7 @@ describe("Errors", () => {
216
233
  });
217
234
 
218
235
  await expect(run(app)).rejects.toThrow(
219
- Errors.dependencyNotFound("Task test.off.the.grid").message
236
+ new DependencyNotFoundError("Task test.off.the.grid").message
220
237
  );
221
238
  });
222
239
 
@@ -244,7 +261,7 @@ describe("Errors", () => {
244
261
  });
245
262
 
246
263
  await expect(run(app)).rejects.toThrow(
247
- Errors.dependencyNotFound("Resource test.off.the.grid").message
264
+ new DependencyNotFoundError("Resource test.off.the.grid").message
248
265
  );
249
266
  });
250
267
 
@@ -254,7 +271,71 @@ describe("Errors", () => {
254
271
  run: async () => {},
255
272
  }).everywhere();
256
273
  expect(() => first.everywhere()).toThrow(
257
- Errors.middlewareAlreadyGlobal("x").message
274
+ new MiddlewareAlreadyGlobalError("x").message
258
275
  );
259
276
  });
277
+
278
+ describe("Error Classes", () => {
279
+ it("should have correct error names and inheritance", () => {
280
+ // Test base RuntimeError
281
+ const baseError = new RuntimeError("test");
282
+ expect(baseError.name).toBe("RuntimeError");
283
+ expect(baseError).toBeInstanceOf(Error);
284
+ expect(baseError).toBeInstanceOf(RuntimeError);
285
+
286
+ // Test DuplicateRegistrationError
287
+ const dupError = new DuplicateRegistrationError("Task", "test");
288
+ expect(dupError.name).toBe("DuplicateRegistrationError");
289
+ expect(dupError).toBeInstanceOf(Error);
290
+ expect(dupError).toBeInstanceOf(RuntimeError);
291
+ expect(dupError).toBeInstanceOf(DuplicateRegistrationError);
292
+
293
+ // Test DependencyNotFoundError
294
+ const depError = new DependencyNotFoundError("test");
295
+ expect(depError.name).toBe("DependencyNotFoundError");
296
+ expect(depError).toBeInstanceOf(RuntimeError);
297
+
298
+ // Test UnknownItemTypeError
299
+ const unknownError = new UnknownItemTypeError("test");
300
+ expect(unknownError.name).toBe("UnknownItemTypeError");
301
+ expect(unknownError).toBeInstanceOf(RuntimeError);
302
+
303
+ // Test CircularDependenciesError
304
+ const circularError = new CircularDependenciesError(["a", "b"]);
305
+ expect(circularError.name).toBe("CircularDependenciesError");
306
+ expect(circularError).toBeInstanceOf(RuntimeError);
307
+
308
+ // Test EventNotFoundError
309
+ const eventError = new EventNotFoundError("test");
310
+ expect(eventError.name).toBe("EventNotFoundError");
311
+ expect(eventError).toBeInstanceOf(RuntimeError);
312
+
313
+ // Test MiddlewareAlreadyGlobalError
314
+ const middlewareError = new MiddlewareAlreadyGlobalError("test");
315
+ expect(middlewareError.name).toBe("MiddlewareAlreadyGlobalError");
316
+ expect(middlewareError).toBeInstanceOf(RuntimeError);
317
+
318
+ // Test LockedError
319
+ const lockedError = new LockedError("test");
320
+ expect(lockedError.name).toBe("LockedError");
321
+ expect(lockedError).toBeInstanceOf(RuntimeError);
322
+
323
+ // Test StoreAlreadyInitializedError
324
+ const storeError = new StoreAlreadyInitializedError();
325
+ expect(storeError.name).toBe("StoreAlreadyInitializedError");
326
+ expect(storeError).toBeInstanceOf(RuntimeError);
327
+
328
+ // Test ValidationError with Error object
329
+ const validationErrorWithError = new ValidationError("Task input", "test-task", new Error("Required field missing"));
330
+ expect(validationErrorWithError.name).toBe("ValidationError");
331
+ expect(validationErrorWithError.message).toBe("Task input validation failed for test-task: Required field missing");
332
+ expect(validationErrorWithError).toBeInstanceOf(RuntimeError);
333
+
334
+ // Test ValidationError with string
335
+ const validationErrorWithString = new ValidationError("Resource config", Symbol("test-resource"), "Invalid configuration");
336
+ expect(validationErrorWithString.name).toBe("ValidationError");
337
+ expect(validationErrorWithString.message).toBe("Resource config validation failed for Symbol(test-resource): Invalid configuration");
338
+ expect(validationErrorWithString).toBeInstanceOf(RuntimeError);
339
+ });
340
+ });
260
341
  });
@@ -4,7 +4,6 @@ import {
4
4
  symbolEvent,
5
5
  symbolFilePath,
6
6
  } from "../../defs";
7
- import { Errors } from "../../errors";
8
7
  import { EventManager } from "../../models/EventManager";
9
8
 
10
9
  describe("EventManager", () => {
@@ -5,12 +5,25 @@ import { globalEvents } from "../../globals/globalEvents";
5
5
  describe("Logger", () => {
6
6
  let logger: Logger;
7
7
  let mockEventManager: jest.Mocked<EventManager>;
8
+ let originalEnv: any;
8
9
 
9
10
  beforeEach(() => {
11
+ // Save original environment
12
+ originalEnv = { ...process.env };
13
+
14
+ // Clear environment variables that might affect tests
15
+ delete process.env.RUNNER_DISABLE_LOGS;
16
+ delete process.env.RUNNER_LOG_LEVEL;
17
+
10
18
  mockEventManager = new EventManager() as jest.Mocked<EventManager>;
11
19
  logger = new Logger(mockEventManager);
12
20
  });
13
21
 
22
+ afterEach(() => {
23
+ // Restore original environment
24
+ process.env = originalEnv;
25
+ });
26
+
14
27
  describe("log method", () => {
15
28
  it("should emit a log event with correct data", async () => {
16
29
  const testData = "Test log message";
@@ -318,7 +331,7 @@ describe("Logger", () => {
318
331
  it("should use bound context in log calls", async () => {
319
332
  const boundContext = { source: "testSource", userId: "123" };
320
333
  const loggerWithContext = new Logger(mockEventManager, boundContext);
321
-
334
+
322
335
  mockEventManager.emit = jest.fn().mockResolvedValue(undefined);
323
336
  mockEventManager.hasListeners = jest.fn().mockReturnValue(true);
324
337
 
@@ -388,6 +401,70 @@ describe("Logger", () => {
388
401
  });
389
402
  });
390
403
 
404
+ describe("default print threshold", () => {
405
+ it("should default to 'info' level when no environment variables are set", () => {
406
+ const logger = new Logger(mockEventManager);
407
+ expect(logger.printThreshold).toBe("info");
408
+ });
409
+
410
+ it("should respect RUNNER_DISABLE_LOGS=true to disable logging", () => {
411
+ process.env.RUNNER_DISABLE_LOGS = "true";
412
+ const logger = new Logger(mockEventManager);
413
+ expect(logger.printThreshold).toBeNull();
414
+ });
415
+
416
+ it("should respect RUNNER_DISABLE_LOGS=1 to disable logging", () => {
417
+ process.env.RUNNER_DISABLE_LOGS = "1";
418
+ const logger = new Logger(mockEventManager);
419
+ expect(logger.printThreshold).toBeNull();
420
+ });
421
+
422
+ it("should respect RUNNER_LOG_LEVEL environment variable", () => {
423
+ process.env.RUNNER_LOG_LEVEL = "error";
424
+ const logger = new Logger(mockEventManager);
425
+ expect(logger.printThreshold).toBe("error");
426
+ });
427
+
428
+ it("should ignore invalid RUNNER_LOG_LEVEL and use default", () => {
429
+ process.env.RUNNER_LOG_LEVEL = "invalid_level";
430
+ const logger = new Logger(mockEventManager);
431
+ expect(logger.printThreshold).toBe("info");
432
+ });
433
+
434
+ it("should prioritize RUNNER_DISABLE_LOGS over RUNNER_LOG_LEVEL", () => {
435
+ process.env.RUNNER_DISABLE_LOGS = "true";
436
+ process.env.RUNNER_LOG_LEVEL = "debug";
437
+ const logger = new Logger(mockEventManager);
438
+ expect(logger.printThreshold).toBeNull();
439
+ });
440
+
441
+ it("should print info level logs by default", () => {
442
+ const consoleLogSpy = jest.spyOn(console, "log").mockImplementation();
443
+
444
+ const logger = new Logger(mockEventManager);
445
+ logger.log("info", "This should be printed by default");
446
+
447
+ expect(consoleLogSpy).toHaveBeenCalledWith(
448
+ expect.stringContaining("This should be printed by default")
449
+ );
450
+
451
+ consoleLogSpy.mockRestore();
452
+ });
453
+
454
+ it("should not print debug level logs by default", () => {
455
+ const consoleLogSpy = jest.spyOn(console, "log").mockImplementation();
456
+
457
+ const logger = new Logger(mockEventManager);
458
+ logger.log("debug", "This should not be printed by default");
459
+
460
+ expect(consoleLogSpy).not.toHaveBeenCalledWith(
461
+ expect.stringContaining("This should not be printed by default")
462
+ );
463
+
464
+ consoleLogSpy.mockRestore();
465
+ });
466
+ });
467
+
391
468
  it("should auto-print logs based on autoPrintLogsAfter option", () => {
392
469
  const autoPrintLevel: LogLevels = "warn";
393
470
  logger.setPrintThreshold(autoPrintLevel);
@@ -429,14 +506,14 @@ describe("Logger", () => {
429
506
 
430
507
  it("should disable auto-printing when setPrintThreshold is set to null", () => {
431
508
  const consoleLogSpy = jest.spyOn(console, "log").mockImplementation();
432
-
509
+
433
510
  // Set to null to disable auto-printing
434
511
  logger.setPrintThreshold(null);
435
-
512
+
436
513
  logger.log("error", "Should not be printed");
437
-
514
+
438
515
  expect(consoleLogSpy).not.toHaveBeenCalled();
439
-
516
+
440
517
  consoleLogSpy.mockRestore();
441
518
  });
442
519
  });
@@ -15,4 +15,4 @@ export const cResource = defineResource({
15
15
  const result: string = await aTask(); // Still benefits of autocompletion
16
16
  return `C depends on ${result}`;
17
17
  },
18
- }) as IResource<void, string>; // This is the key change.
18
+ }) as IResource<void, Promise<string>>; // This is the key change.
@@ -6,7 +6,7 @@ import {
6
6
  defineMiddleware,
7
7
  defineOverride,
8
8
  } from "../define";
9
- import { Errors } from "../errors";
9
+ import { DependencyNotFoundError } from "../errors";
10
10
  import { run } from "../run";
11
11
 
12
12
  describe("run.overrides", () => {
@@ -230,7 +230,7 @@ describe("run.overrides", () => {
230
230
  });
231
231
 
232
232
  await expect(run(app)).rejects.toThrowError(
233
- Errors.dependencyNotFound("task2").message
233
+ new DependencyNotFoundError("task2").message
234
234
  );
235
235
  });
236
236
 
@@ -256,7 +256,7 @@ describe("run.overrides", () => {
256
256
  });
257
257
 
258
258
  await expect(run(app)).rejects.toThrowError(
259
- Errors.dependencyNotFound("override2").message
259
+ new DependencyNotFoundError("override2").message
260
260
  );
261
261
  });
262
262
 
@@ -6,15 +6,12 @@ import {
6
6
  defineOverride,
7
7
  defineTag,
8
8
  } from "../define";
9
+ import { IMeta } from "../defs";
9
10
  import {
10
- IEventDefinition,
11
- IMiddlewareDefinition,
12
- IResource,
13
- IResourceWithConfig,
14
- ITaskDefinition,
15
- RegisterableItems,
16
- } from "../defs";
17
- import { createTestResource } from "..";
11
+ EnsureResponseSatisfiesContracts,
12
+ HasContracts,
13
+ } from "../defs.returnTag";
14
+ import { createTestResource, run } from "..";
18
15
 
19
16
  // This is skipped because we mostly check typesafety.
20
17
  describe.skip("typesafety", () => {
@@ -244,7 +241,7 @@ describe.skip("typesafety", () => {
244
241
  const harness = createTestResource(app);
245
242
 
246
243
  // Types: input must match, override deps must match, output is awaited number
247
- const { value: t } = await (await import("../run")).run(harness);
244
+ const { value: t } = await run(harness);
248
245
  const r1: number | undefined = await t.runTask(add, { x: 1 });
249
246
  // @ts-expect-error wrong input type
250
247
  await t.runTask(add, { z: 1 });
@@ -313,8 +310,114 @@ describe.skip("typesafety", () => {
313
310
  tag3,
314
311
  ],
315
312
  },
313
+ run: async (input) => {
314
+ return input;
315
+ },
316
316
  });
317
317
 
318
318
  expect(true).toBe(true);
319
319
  });
320
+
321
+ it("should enforce contracts on tasks", async () => {
322
+ interface IUser {
323
+ name: string;
324
+ }
325
+
326
+ interface IOther {
327
+ age: number;
328
+ }
329
+
330
+ const tag = defineTag<{ value: number }, IUser>({ id: "tag" });
331
+ const tag2 = defineTag<void, IOther>({ id: "tag2" });
332
+
333
+ const meta = {
334
+ tags: [tag.with({ value: 123 }), tag2, "string"],
335
+ } satisfies IMeta;
336
+
337
+ const response = {
338
+ age: 123,
339
+ name: "123", // intentional
340
+ };
341
+ type TEST = HasContracts<typeof meta>;
342
+ type TEST2 = EnsureResponseSatisfiesContracts<typeof meta, typeof response>;
343
+
344
+ const task = defineTask({
345
+ id: "task",
346
+ meta,
347
+ run: async (input: { name: string }) => {
348
+ return {
349
+ age: 123,
350
+ name: "123",
351
+ };
352
+ },
353
+ });
354
+ const task2 = defineTask({
355
+ id: "task",
356
+ meta,
357
+ // @ts-expect-error
358
+ run: async (input: { name: string }) => {
359
+ return {
360
+ age: "123",
361
+ };
362
+ },
363
+ });
364
+
365
+ const task3 = defineTask({
366
+ id: "task",
367
+ meta,
368
+ // @ts-expect-error
369
+ run: async (input: { name: string }) => {
370
+ return {};
371
+ },
372
+ });
373
+ });
374
+
375
+ it("should enforce contracts on resources", async () => {
376
+ interface IUser {
377
+ name: string;
378
+ }
379
+
380
+ interface IOther {
381
+ age: number;
382
+ }
383
+
384
+ const tag = defineTag<{ value: number }, IUser>({ id: "tag" });
385
+ const tag2 = defineTag<void, IOther>({ id: "tag2" });
386
+
387
+ const meta = {
388
+ tags: [tag.with({ value: 123 }), tag2, "string"],
389
+ } satisfies IMeta;
390
+
391
+ const resourceOk = defineResource({
392
+ id: "resource.ok",
393
+ meta,
394
+ init: async () => {
395
+ return {
396
+ age: 123,
397
+ name: "123",
398
+ };
399
+ },
400
+ });
401
+
402
+ const resourceBad1 = defineResource({
403
+ id: "resource.bad1",
404
+ meta,
405
+ // @ts-expect-error
406
+ init: async () => {
407
+ return {
408
+ age: "123",
409
+ name: "123",
410
+ };
411
+ },
412
+ });
413
+
414
+ const resourceBad2 = defineResource({
415
+ id: "resource.bad2",
416
+ meta,
417
+ // @ts-expect-error
418
+ init: async () => {
419
+ return {};
420
+ },
421
+ });
422
+ });
320
423
  });
@@ -0,0 +1,111 @@
1
+ import { defineTask, defineResource, defineEvent, defineMiddleware } from "../define";
2
+ import { run } from "../run";
3
+ import { ValidationError } from "../errors";
4
+ import { IValidationSchema } from "../defs";
5
+
6
+ // Mock validation schema similar to the existing pattern
7
+ class MockValidationSchema<T> implements IValidationSchema<T> {
8
+ constructor(
9
+ private validator: (input: unknown) => T,
10
+ ) {}
11
+
12
+ parse(input: unknown): T {
13
+ return this.validator(input);
14
+ }
15
+ }
16
+
17
+ describe("Validation Edge Cases", () => {
18
+ it("should handle non-Error thrown from task input validation", async () => {
19
+ const taskSchema = new MockValidationSchema<string>((input: unknown) => {
20
+ // Throw a non-Error object to trigger the instanceof Error === false branch
21
+ throw "Non-error string thrown";
22
+ });
23
+
24
+ const task = defineTask({
25
+ id: "task.nonErrorValidation",
26
+ inputSchema: taskSchema,
27
+ run: async (input: string) => "success",
28
+ });
29
+
30
+ const app = defineResource({
31
+ id: "app",
32
+ register: [task],
33
+ dependencies: { task },
34
+ init: async (_, { task }) => {
35
+ await task("invalid");
36
+ },
37
+ });
38
+
39
+ await expect(run(app)).rejects.toThrow(ValidationError);
40
+ await expect(run(app)).rejects.toThrow("Task input validation failed for task.nonErrorValidation: Non-error string thrown");
41
+ });
42
+
43
+ it("should handle non-Error thrown from resource config validation", async () => {
44
+ const configSchema = new MockValidationSchema<any>((input: unknown) => {
45
+ throw "Resource config error string";
46
+ });
47
+
48
+ const resource = defineResource({
49
+ id: "resource.nonErrorValidation",
50
+ configSchema: configSchema,
51
+ init: async (config: any) => "success",
52
+ });
53
+
54
+ expect(() => {
55
+ resource.with({ invalid: "config" } as any);
56
+ }).toThrow(ValidationError);
57
+ expect(() => {
58
+ resource.with({ invalid: "config" } as any);
59
+ }).toThrow("Resource config validation failed for resource.nonErrorValidation: Resource config error string");
60
+ });
61
+
62
+ it("should handle non-Error thrown from middleware config validation", async () => {
63
+ const configSchema = new MockValidationSchema<any>((input: unknown) => {
64
+ throw "Middleware config error string";
65
+ });
66
+
67
+ const middleware = defineMiddleware({
68
+ id: "middleware.nonErrorValidation",
69
+ configSchema: configSchema,
70
+ run: async ({ next }) => next(),
71
+ });
72
+
73
+ expect(() => {
74
+ middleware.with({ invalid: "config" } as any);
75
+ }).toThrow(ValidationError);
76
+ expect(() => {
77
+ middleware.with({ invalid: "config" } as any);
78
+ }).toThrow("Middleware config validation failed for middleware.nonErrorValidation: Middleware config error string");
79
+ });
80
+
81
+ it("should handle non-Error thrown from event payload validation", async () => {
82
+ const payloadSchema = new MockValidationSchema<any>((input: unknown) => {
83
+ throw "Event payload error string";
84
+ });
85
+
86
+ const event = defineEvent({
87
+ id: "event.nonErrorValidation",
88
+ payloadSchema: payloadSchema,
89
+ });
90
+
91
+ const listenerTask = defineTask({
92
+ id: "task.listener",
93
+ on: event,
94
+ run: async (event) => {
95
+ // This won't be called because validation will fail
96
+ },
97
+ });
98
+
99
+ const app = defineResource({
100
+ id: "app",
101
+ register: [event, listenerTask],
102
+ dependencies: { event },
103
+ init: async (_, { event }) => {
104
+ await event({ invalid: "payload" });
105
+ },
106
+ });
107
+
108
+ await expect(run(app)).rejects.toThrow(ValidationError);
109
+ await expect(run(app)).rejects.toThrow("Event payload validation failed for event.nonErrorValidation: Event payload error string");
110
+ });
111
+ });