@ontrails/core 1.0.0-beta.0 → 1.0.0-beta.10

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 (84) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +109 -0
  3. package/README.md +101 -131
  4. package/dist/derive.d.ts +1 -1
  5. package/dist/derive.d.ts.map +1 -1
  6. package/dist/derive.js +4 -1
  7. package/dist/derive.js.map +1 -1
  8. package/dist/dispatch.d.ts +27 -0
  9. package/dist/dispatch.d.ts.map +1 -0
  10. package/dist/dispatch.js +34 -0
  11. package/dist/dispatch.js.map +1 -0
  12. package/dist/event.d.ts +2 -2
  13. package/dist/event.d.ts.map +1 -1
  14. package/dist/event.js +1 -1
  15. package/dist/event.js.map +1 -1
  16. package/dist/execute.d.ts +30 -0
  17. package/dist/execute.d.ts.map +1 -0
  18. package/dist/execute.js +64 -0
  19. package/dist/execute.js.map +1 -0
  20. package/dist/index.d.ts +10 -6
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +7 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/patterns/status.d.ts +1 -1
  25. package/dist/result.d.ts +11 -11
  26. package/dist/result.d.ts.map +1 -1
  27. package/dist/result.js +15 -4
  28. package/dist/result.js.map +1 -1
  29. package/dist/serialization.d.ts.map +1 -1
  30. package/dist/serialization.js +45 -7
  31. package/dist/serialization.js.map +1 -1
  32. package/dist/topo.d.ts +4 -4
  33. package/dist/topo.d.ts.map +1 -1
  34. package/dist/topo.js +12 -16
  35. package/dist/topo.js.map +1 -1
  36. package/dist/trail.d.ts +16 -10
  37. package/dist/trail.d.ts.map +1 -1
  38. package/dist/trail.js +4 -2
  39. package/dist/trail.js.map +1 -1
  40. package/dist/type-utils.d.ts +24 -0
  41. package/dist/type-utils.d.ts.map +1 -0
  42. package/dist/type-utils.js +12 -0
  43. package/dist/type-utils.js.map +1 -0
  44. package/dist/types.d.ts +1 -2
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/validate-topo.d.ts +2 -2
  47. package/dist/validate-topo.d.ts.map +1 -1
  48. package/dist/validate-topo.js +59 -9
  49. package/dist/validate-topo.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/__tests__/context.test.ts +4 -5
  52. package/src/__tests__/derive.test.ts +44 -0
  53. package/src/__tests__/dispatch.test.ts +154 -0
  54. package/src/__tests__/event.test.ts +5 -5
  55. package/src/__tests__/execute.test.ts +208 -0
  56. package/src/__tests__/layer.test.ts +11 -111
  57. package/src/__tests__/serialization.test.ts +166 -1
  58. package/src/__tests__/topo.test.ts +101 -79
  59. package/src/__tests__/trail.test.ts +73 -35
  60. package/src/__tests__/type-utils.test.ts +90 -0
  61. package/src/__tests__/validate-topo.test.ts +97 -20
  62. package/src/derive.ts +12 -2
  63. package/src/dispatch.ts +54 -0
  64. package/src/event.ts +3 -3
  65. package/src/execute.ts +96 -0
  66. package/src/index.ts +22 -18
  67. package/src/result.ts +29 -15
  68. package/src/serialization.ts +56 -11
  69. package/src/topo.ts +18 -23
  70. package/src/trail.ts +24 -13
  71. package/src/type-utils.ts +45 -0
  72. package/src/types.ts +1 -3
  73. package/src/validate-topo.ts +70 -10
  74. package/tsconfig.tsbuildinfo +1 -1
  75. package/dist/hike.d.ts +0 -36
  76. package/dist/hike.d.ts.map +0 -1
  77. package/dist/hike.js +0 -20
  78. package/dist/hike.js.map +0 -1
  79. package/src/__tests__/hike.test.ts +0 -117
  80. package/src/__tests__/job.test.ts +0 -98
  81. package/src/adapters.ts +0 -68
  82. package/src/health.ts +0 -23
  83. package/src/hike.ts +0 -77
  84. package/src/job.ts +0 -20
package/dist/trail.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"trail.js","sourceRoot":"","sources":["../src/trail.ts"],"names":[],"mappings":"AAqGA,MAAM,UAAU,KAAK,CACnB,QAA8D,EAC9D,SAA2B;IAE3B,MAAM,QAAQ,GACZ,OAAO,QAAQ,KAAK,QAAQ;QAC1B,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE;QACnC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAE1C,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,EAAE,cAAc,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC;IAElD,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,GAAG,IAAI;QACP,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,cAAc,EAAE,KAAK,EAAE,KAAQ,EAAE,GAAiB,EAAE,EAAE,CACpD,MAAM,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC;QAClC,IAAI,EAAE,OAAgB;KACvB,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"trail.js","sourceRoot":"","sources":["../src/trail.ts"],"names":[],"mappings":"AA+GA,MAAM,UAAU,KAAK,CACnB,QAA8D,EAC9D,SAA2B;IAE3B,MAAM,QAAQ,GACZ,OAAO,QAAQ,KAAK,QAAQ;QAC1B,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE;QACnC,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAE1C,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC;IAE7E,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,GAAG,IAAI;QACP,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;QAC7C,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,MAAM,EAAE,SAAS,IAAI,OAAO;QAC5B,IAAI,EAAE,OAAgB;QACtB,GAAG,EAAE,KAAK,EAAE,KAAQ,EAAE,GAAiB,EAAE,EAAE,CAAC,MAAM,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;KAClE,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Type utilities for extracting input/output types from trails.
3
+ */
4
+ import type { Result } from './result.js';
5
+ import type { AnyTrail, Trail } from './trail.js';
6
+ /** Extract the input type from a Trail. */
7
+ export type TrailInput<T extends AnyTrail> = T extends Trail<infer I, any> ? I : never;
8
+ /** Extract the output type from a Trail. */
9
+ export type TrailOutput<T extends AnyTrail> = T extends Trail<any, infer O> ? O : never;
10
+ /**
11
+ * Extracts the full `Result<Output, Error>` type from a trail definition.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * type SearchResult = TrailResult<typeof searchTrail>;
16
+ * // Result<{ results: Item[]; count: number }, Error>
17
+ * ```
18
+ */
19
+ export type TrailResult<T extends AnyTrail> = Result<TrailOutput<T>, Error>;
20
+ /** Get the input Zod schema from a trail, preserving the specific schema type. */
21
+ export declare const inputOf: <T extends AnyTrail>(trail: T) => T["input"];
22
+ /** Get the output Zod schema from a trail, if defined, preserving the specific schema type. */
23
+ export declare const outputOf: <T extends AnyTrail>(trail: T) => T["output"];
24
+ //# sourceMappingURL=type-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-utils.d.ts","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAQlD,2CAA2C;AAC3C,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,QAAQ,IACvC,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE5C,4CAA4C;AAC5C,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,QAAQ,IACxC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE5C;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,QAAQ,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAQ5E,kFAAkF;AAClF,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,QAAQ,EAAE,OAAO,CAAC,KAAG,CAAC,CAAC,OAAO,CACnD,CAAC;AAEd,+FAA+F;AAC/F,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,QAAQ,EAAE,OAAO,CAAC,KAAG,CAAC,CAAC,QAAQ,CACpD,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Type utilities for extracting input/output types from trails.
3
+ */
4
+ /* oxlint-enable no-explicit-any */
5
+ // ---------------------------------------------------------------------------
6
+ // Runtime schema accessors
7
+ // ---------------------------------------------------------------------------
8
+ /** Get the input Zod schema from a trail, preserving the specific schema type. */
9
+ export const inputOf = (trail) => trail.input;
10
+ /** Get the output Zod schema from a trail, if defined, preserving the specific schema type. */
11
+ export const outputOf = (trail) => trail.output;
12
+ //# sourceMappingURL=type-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-utils.js","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AA8BH,mCAAmC;AAEnC,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E,kFAAkF;AAClF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAqB,KAAQ,EAAc,EAAE,CAClE,KAAK,CAAC,KAAK,CAAC;AAEd,+FAA+F;AAC/F,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAqB,KAAQ,EAAe,EAAE,CACpE,KAAK,CAAC,MAAM,CAAC"}
package/dist/types.d.ts CHANGED
@@ -40,7 +40,6 @@ export interface TrailContext {
40
40
  readonly progress?: ProgressCallback | undefined;
41
41
  readonly cwd?: string | undefined;
42
42
  readonly env?: Record<string, string | undefined> | undefined;
43
- readonly [key: string]: unknown;
43
+ readonly extensions?: Readonly<Record<string, unknown>> | undefined;
44
44
  }
45
- export type Surface = 'cli' | 'mcp' | 'http' | 'ws';
46
45
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Structural validation for a Topo graph.
3
3
  *
4
- * Checks hike follows references, example input validity, event origin
4
+ * Checks trail follow references, example input validity, event origin
5
5
  * references, and output schema completeness. Returns a Result with all
6
6
  * issues collected into a single ValidationError.
7
7
  */
@@ -16,7 +16,7 @@ export interface TopoIssue {
16
16
  /**
17
17
  * Validate the structural integrity of a Topo graph.
18
18
  *
19
- * Checks follows references, example inputs, event origins, and output
19
+ * Checks follow references, example inputs, event origins, and output
20
20
  * schema presence. Returns `Result.ok()` when no issues are found, or
21
21
  * `Result.err(ValidationError)` with all issues in the error context.
22
22
  */
@@ -1 +1 @@
1
- {"version":3,"file":"validate-topo.d.ts","sourceRoot":"","sources":["../src/validate-topo.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQtC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAoGD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,IAAI,KAAG,MAAM,CAAC,IAAI,EAAE,eAAe,CAmBrE,CAAC"}
1
+ {"version":3,"file":"validate-topo.d.ts","sourceRoot":"","sources":["../src/validate-topo.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQtC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAiKD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,IAAI,KAAG,MAAM,CAAC,IAAI,EAAE,eAAe,CAmBrE,CAAC"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Structural validation for a Topo graph.
3
3
  *
4
- * Checks hike follows references, example input validity, event origin
4
+ * Checks trail follow references, example input validity, event origin
5
5
  * references, and output schema completeness. Returns a Result with all
6
6
  * issues collected into a single ValidationError.
7
7
  */
@@ -11,13 +11,62 @@ import { validateInput } from './validation.js';
11
11
  // ---------------------------------------------------------------------------
12
12
  // Validators
13
13
  // ---------------------------------------------------------------------------
14
- const checkFollows = (hikes, topo) => {
14
+ const WHITE = 0;
15
+ const GRAY = 1;
16
+ const BLACK = 2;
17
+ /** Build an adjacency list and initial color map from trails with follow. */
18
+ const buildFollowGraph = (trails) => {
19
+ const graph = new Map();
20
+ for (const [id, t] of trails) {
21
+ if (t.follow.length > 0) {
22
+ graph.set(id, t.follow);
23
+ }
24
+ }
25
+ const color = new Map();
26
+ for (const id of graph.keys()) {
27
+ color.set(id, WHITE);
28
+ }
29
+ return { color, graph };
30
+ };
31
+ /** Detect multi-node cycles in the trail follow graph via DFS. */
32
+ const detectFollowCycles = (trails) => {
33
+ const issues = [];
34
+ const { color, graph } = buildFollowGraph(trails);
35
+ const dfs = (node, path) => {
36
+ color.set(node, GRAY);
37
+ for (const next of graph.get(node) ?? []) {
38
+ if (!graph.has(next)) {
39
+ continue;
40
+ }
41
+ const c = color.get(next) ?? WHITE;
42
+ if (c === GRAY) {
43
+ const cycle = [...path.slice(path.indexOf(next)), next];
44
+ issues.push({
45
+ message: `Cycle detected: ${cycle.join(' → ')}`,
46
+ rule: 'follow-cycle',
47
+ trailId: next,
48
+ });
49
+ }
50
+ else if (c === WHITE) {
51
+ dfs(next, [...path, next]);
52
+ }
53
+ }
54
+ color.set(node, BLACK);
55
+ };
56
+ for (const id of graph.keys()) {
57
+ if (color.get(id) === WHITE) {
58
+ dfs(id, [id]);
59
+ }
60
+ }
61
+ return issues;
62
+ };
63
+ const checkFollows = (trails, topo) => {
15
64
  const issues = [];
16
- for (const [id, hike] of hikes) {
17
- for (const followId of hike.follows) {
65
+ for (const [id, trail] of trails) {
66
+ for (const followId of trail.follow) {
18
67
  if (followId === id) {
19
68
  issues.push({
20
- message: `Hike follows itself`,
69
+ message: `Trail follows itself`,
21
70
  rule: 'no-self-follow',
22
71
  trailId: id,
23
72
  });
@@ -25,18 +74,19 @@ const checkFollows = (hikes, topo) => {
25
74
  else if (!topo.has(followId)) {
26
75
  issues.push({
27
76
  message: `Follows "${followId}" which is not in the topo`,
28
- rule: 'follows-exist',
77
+ rule: 'follow-exists',
29
78
  trailId: id,
30
79
  });
31
80
  }
32
81
  }
33
82
  }
83
+ issues.push(...detectFollowCycles(trails));
34
84
  return issues;
35
85
  };
36
86
  const checkOneExample = (id, example, inputSchema, hasOutput) => {
37
87
  const issues = [];
38
88
  const result = validateInput(inputSchema, example.input);
39
- if (result.isErr() && example.error === undefined) {
89
+ if (result.isErr() && example.error !== 'ValidationError') {
40
90
  issues.push({
41
91
  message: `Example "${example.name}" input does not parse against schema`,
42
92
  rule: 'example-input-valid',
@@ -88,13 +138,13 @@ const checkEventOrigins = (events, topo) => {
88
138
  /**
89
139
  * Validate the structural integrity of a Topo graph.
90
140
  *
91
- * Checks follows references, example inputs, event origins, and output
141
+ * Checks follow references, example inputs, event origins, and output
92
142
  * schema presence. Returns `Result.ok()` when no issues are found, or
93
143
  * `Result.err(ValidationError)` with all issues in the error context.
94
144
  */
95
145
  export const validateTopo = (topo) => {
96
146
  const issues = [
97
- ...checkFollows(topo.hikes, topo),
147
+ ...checkFollows(topo.trails, topo),
98
148
  ...checkExamples(topo.trails),
99
149
  ...checkEventOrigins(topo.events, topo),
100
150
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"validate-topo.js","sourceRoot":"","sources":["../src/validate-topo.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validate-topo.js","sourceRoot":"","sources":["../src/validate-topo.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/core",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.10",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -16,6 +16,6 @@
16
16
  "clean": "rm -rf dist *.tsbuildinfo"
17
17
  },
18
18
  "peerDependencies": {
19
- "zod": "catalog:"
19
+ "zod": "^4.3.5"
20
20
  }
21
21
  }
@@ -48,14 +48,13 @@ describe('createTrailContext', () => {
48
48
  expect(ctx.workspaceRoot).toBe('/tmp');
49
49
  });
50
50
 
51
- test('TrailContext is extensible with custom fields', () => {
51
+ test('TrailContext supports extensions for custom fields', () => {
52
52
  const ctx: TrailContext = createTrailContext({
53
- customField: 42,
54
- nested: { key: 'value' },
53
+ extensions: { customField: 42, nested: { key: 'value' } },
55
54
  });
56
55
 
57
- expect(ctx['customField']).toBe(42);
58
- expect(ctx['nested']).toEqual({ key: 'value' });
56
+ expect(ctx.extensions?.['customField']).toBe(42);
57
+ expect(ctx.extensions?.['nested']).toEqual({ key: 'value' });
59
58
  });
60
59
 
61
60
  test('each call generates a unique requestId', () => {
@@ -77,6 +77,50 @@ describe('derive', () => {
77
77
  });
78
78
  });
79
79
 
80
+ describe('array types', () => {
81
+ test('z.array(z.string()) derives string[] field', () => {
82
+ const schema = z.object({ names: z.array(z.string()) });
83
+ const fields = deriveFields(schema);
84
+ expect(fields[0]).toMatchObject({
85
+ name: 'names',
86
+ options: undefined,
87
+ required: true,
88
+ type: 'string[]',
89
+ });
90
+ });
91
+
92
+ test('z.array(z.number()) derives number[] field', () => {
93
+ const schema = z.object({ scores: z.array(z.number()) });
94
+ const fields = deriveFields(schema);
95
+ expect(fields[0]).toMatchObject({
96
+ name: 'scores',
97
+ options: undefined,
98
+ required: true,
99
+ type: 'number[]',
100
+ });
101
+ });
102
+
103
+ test('optional z.array(z.string()) marks not required', () => {
104
+ const schema = z.object({ tags: z.array(z.string()).optional() });
105
+ const fields = deriveFields(schema);
106
+ expect(fields[0]).toMatchObject({
107
+ name: 'tags',
108
+ required: false,
109
+ type: 'string[]',
110
+ });
111
+ });
112
+
113
+ test('z.array(z.string()) with default sets default value', () => {
114
+ const schema = z.object({
115
+ items: z.array(z.string()).default(['a', 'b']),
116
+ });
117
+ const fields = deriveFields(schema);
118
+ expect(fields[0]?.required).toBe(false);
119
+ expect(fields[0]?.type).toBe('string[]');
120
+ expect(fields[0]?.default).toEqual(['a', 'b']);
121
+ });
122
+ });
123
+
80
124
  describe('modifiers', () => {
81
125
  test('.describe() sets label', () => {
82
126
  const schema = z.object({
@@ -0,0 +1,154 @@
1
+ /* oxlint-disable require-await -- trail implementations satisfy async interface without awaiting */
2
+ import { describe, test, expect } from 'bun:test';
3
+
4
+ import { z } from 'zod';
5
+
6
+ import { dispatch } from '../dispatch';
7
+ import { InternalError, NotFoundError, ValidationError } from '../errors';
8
+ import type { Layer } from '../layer';
9
+ import { Result } from '../result';
10
+ import { topo } from '../topo';
11
+ import { trail } from '../trail';
12
+ import type { TrailContext } from '../types';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Fixtures
16
+ // ---------------------------------------------------------------------------
17
+
18
+ const echoTrail = trail('echo', {
19
+ input: z.object({ value: z.string() }),
20
+ output: z.object({ value: z.string() }),
21
+ run: (input) => Result.ok({ value: input.value }),
22
+ });
23
+
24
+ const throwingTrail = trail('throws', {
25
+ input: z.object({}),
26
+ run: () => {
27
+ throw new Error('kaboom');
28
+ },
29
+ });
30
+
31
+ const testTopo = topo('test', { echoTrail, throwingTrail });
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Tests
35
+ // ---------------------------------------------------------------------------
36
+
37
+ describe('dispatch', () => {
38
+ describe('happy path', () => {
39
+ test('dispatches by ID and returns Result.ok with expected value', async () => {
40
+ const result = await dispatch(testTopo, 'echo', { value: 'hello' });
41
+
42
+ expect(result.isOk()).toBe(true);
43
+ expect(result.unwrap()).toEqual({ value: 'hello' });
44
+ });
45
+ });
46
+
47
+ describe('not found', () => {
48
+ test('returns NotFoundError for unknown trail ID', async () => {
49
+ const result = await dispatch(testTopo, 'nonexistent', {});
50
+
51
+ expect(result.isErr()).toBe(true);
52
+ expect(result.error).toBeInstanceOf(NotFoundError);
53
+ expect(result.error.message).toContain('nonexistent');
54
+ expect(result.error.message).toContain('test');
55
+ });
56
+ });
57
+
58
+ describe('validation', () => {
59
+ test('returns ValidationError for invalid input', async () => {
60
+ const result = await dispatch(testTopo, 'echo', { value: 42 });
61
+
62
+ expect(result.isErr()).toBe(true);
63
+ expect(result.error).toBeInstanceOf(ValidationError);
64
+ });
65
+ });
66
+
67
+ describe('layers', () => {
68
+ test('layer composition works through dispatch', async () => {
69
+ const log: string[] = [];
70
+ const layer: Layer = {
71
+ name: 'test-layer',
72
+ wrap(_trail, impl) {
73
+ return async (input, ctx) => {
74
+ log.push('before');
75
+ const r = await impl(input, ctx);
76
+ log.push('after');
77
+ return r;
78
+ };
79
+ },
80
+ };
81
+
82
+ const result = await dispatch(
83
+ testTopo,
84
+ 'echo',
85
+ { value: 'x' },
86
+ { layers: [layer] }
87
+ );
88
+
89
+ expect(result.isOk()).toBe(true);
90
+ expect(log).toEqual(['before', 'after']);
91
+ });
92
+ });
93
+
94
+ describe('context', () => {
95
+ test('context overrides work through dispatch', async () => {
96
+ let capturedCtx: TrailContext | undefined;
97
+ const ctxTrail = trail('ctx-dispatch-test', {
98
+ input: z.object({}),
99
+ run: (_input, ctx) => {
100
+ capturedCtx = ctx;
101
+ return Result.ok(null);
102
+ },
103
+ });
104
+
105
+ const ctxTopo = topo('ctx-test', { ctxTrail });
106
+ await dispatch(
107
+ ctxTopo,
108
+ 'ctx-dispatch-test',
109
+ {},
110
+ { ctx: { requestId: 'override-id' } }
111
+ );
112
+
113
+ expect(capturedCtx?.requestId).toBe('override-id');
114
+ });
115
+
116
+ test('createContext factory works through dispatch', async () => {
117
+ let capturedCtx: TrailContext | undefined;
118
+ const ctxTrail = trail('factory-dispatch-test', {
119
+ input: z.object({}),
120
+ run: (_input, ctx) => {
121
+ capturedCtx = ctx;
122
+ return Result.ok(null);
123
+ },
124
+ });
125
+
126
+ const ctxTopo = topo('factory-test', { ctxTrail });
127
+ const customCtx: TrailContext = {
128
+ cwd: '/custom',
129
+ requestId: 'factory-id',
130
+ signal: new AbortController().signal,
131
+ };
132
+
133
+ await dispatch(
134
+ ctxTopo,
135
+ 'factory-dispatch-test',
136
+ {},
137
+ { createContext: () => customCtx }
138
+ );
139
+
140
+ expect(capturedCtx?.requestId).toBe('factory-id');
141
+ expect(capturedCtx?.cwd).toBe('/custom');
142
+ });
143
+ });
144
+
145
+ describe('error handling', () => {
146
+ test('never throws — exceptions become InternalError', async () => {
147
+ const result = await dispatch(testTopo, 'throws', {});
148
+
149
+ expect(result.isErr()).toBe(true);
150
+ expect(result.error).toBeInstanceOf(InternalError);
151
+ expect(result.error.message).toBe('kaboom');
152
+ });
153
+ });
154
+ });
@@ -12,7 +12,7 @@ const payloadSchema = z.object({
12
12
  const userAction = event('user.action', {
13
13
  description: 'A user performed an action',
14
14
  from: ['auth.login', 'auth.signup'],
15
- markers: { domain: 'auth', priority: 1 },
15
+ metadata: { domain: 'auth', priority: 1 },
16
16
  payload: payloadSchema,
17
17
  });
18
18
 
@@ -45,7 +45,7 @@ describe('event() basics', () => {
45
45
  });
46
46
  });
47
47
 
48
- describe('event() from and markers', () => {
48
+ describe('event() from and metadata', () => {
49
49
  test('preserves from', () => {
50
50
  expect(userAction.from).toEqual(['auth.login', 'auth.signup']);
51
51
  });
@@ -54,8 +54,8 @@ describe('event() from and markers', () => {
54
54
  expect(Object.isFrozen(userAction.from)).toBe(true);
55
55
  });
56
56
 
57
- test('preserves markers', () => {
58
- expect(userAction.markers).toEqual({ domain: 'auth', priority: 1 });
57
+ test('preserves metadata', () => {
58
+ expect(userAction.metadata).toEqual({ domain: 'auth', priority: 1 });
59
59
  });
60
60
 
61
61
  test('optional fields default to undefined', () => {
@@ -64,7 +64,7 @@ describe('event() from and markers', () => {
64
64
  });
65
65
  expect(minimal.description).toBeUndefined();
66
66
  expect(minimal.from).toBeUndefined();
67
- expect(minimal.markers).toBeUndefined();
67
+ expect(minimal.metadata).toBeUndefined();
68
68
  });
69
69
  });
70
70