@ontrails/core 1.0.0-beta.1 → 1.0.0-beta.11

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 (99) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +121 -0
  3. package/README.md +54 -11
  4. package/dist/context.d.ts +2 -2
  5. package/dist/context.d.ts.map +1 -1
  6. package/dist/context.js +12 -7
  7. package/dist/context.js.map +1 -1
  8. package/dist/derive.d.ts +1 -1
  9. package/dist/derive.d.ts.map +1 -1
  10. package/dist/derive.js +4 -1
  11. package/dist/derive.js.map +1 -1
  12. package/dist/dispatch.d.ts +27 -0
  13. package/dist/dispatch.d.ts.map +1 -0
  14. package/dist/dispatch.js +34 -0
  15. package/dist/dispatch.js.map +1 -0
  16. package/dist/event.d.ts +2 -2
  17. package/dist/event.d.ts.map +1 -1
  18. package/dist/event.js +1 -1
  19. package/dist/event.js.map +1 -1
  20. package/dist/execute.d.ts +33 -0
  21. package/dist/execute.d.ts.map +1 -0
  22. package/dist/execute.js +207 -0
  23. package/dist/execute.js.map +1 -0
  24. package/dist/index.d.ts +12 -6
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +9 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/patterns/status.d.ts +1 -1
  29. package/dist/result.d.ts.map +1 -1
  30. package/dist/result.js +15 -4
  31. package/dist/result.js.map +1 -1
  32. package/dist/serialization.d.ts.map +1 -1
  33. package/dist/serialization.js +45 -7
  34. package/dist/serialization.js.map +1 -1
  35. package/dist/service.d.ts +69 -0
  36. package/dist/service.d.ts.map +1 -0
  37. package/dist/service.js +56 -0
  38. package/dist/service.js.map +1 -0
  39. package/dist/topo.d.ts +11 -4
  40. package/dist/topo.d.ts.map +1 -1
  41. package/dist/topo.js +43 -18
  42. package/dist/topo.js.map +1 -1
  43. package/dist/trail.d.ts +21 -10
  44. package/dist/trail.d.ts.map +1 -1
  45. package/dist/trail.js +5 -2
  46. package/dist/trail.js.map +1 -1
  47. package/dist/type-utils.d.ts +24 -0
  48. package/dist/type-utils.d.ts.map +1 -0
  49. package/dist/type-utils.js +12 -0
  50. package/dist/type-utils.js.map +1 -0
  51. package/dist/types.d.ts +10 -2
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/validate-topo.d.ts +2 -2
  54. package/dist/validate-topo.d.ts.map +1 -1
  55. package/dist/validate-topo.js +75 -9
  56. package/dist/validate-topo.js.map +1 -1
  57. package/dist/validation.d.ts.map +1 -1
  58. package/dist/validation.js +34 -3
  59. package/dist/validation.js.map +1 -1
  60. package/package.json +2 -2
  61. package/src/__tests__/context.test.ts +16 -5
  62. package/src/__tests__/derive.test.ts +44 -0
  63. package/src/__tests__/dispatch.test.ts +181 -0
  64. package/src/__tests__/event.test.ts +5 -5
  65. package/src/__tests__/execute.test.ts +523 -0
  66. package/src/__tests__/layer.test.ts +14 -113
  67. package/src/__tests__/serialization.test.ts +166 -1
  68. package/src/__tests__/service.test.ts +197 -0
  69. package/src/__tests__/topo.test.ts +171 -78
  70. package/src/__tests__/trail.test.ts +119 -37
  71. package/src/__tests__/type-utils.test.ts +90 -0
  72. package/src/__tests__/validate-topo.test.ts +140 -19
  73. package/src/__tests__/validation.test.ts +53 -0
  74. package/src/context.ts +18 -9
  75. package/src/derive.ts +12 -2
  76. package/src/dispatch.ts +54 -0
  77. package/src/event.ts +3 -3
  78. package/src/execute.ts +345 -0
  79. package/src/index.ts +39 -18
  80. package/src/result.ts +18 -4
  81. package/src/serialization.ts +56 -11
  82. package/src/service.ts +139 -0
  83. package/src/topo.ts +66 -27
  84. package/src/trail.ts +36 -13
  85. package/src/type-utils.ts +45 -0
  86. package/src/types.ts +11 -2
  87. package/src/validate-topo.ts +92 -10
  88. package/src/validation.ts +35 -3
  89. package/tsconfig.tsbuildinfo +1 -1
  90. package/dist/hike.d.ts +0 -36
  91. package/dist/hike.d.ts.map +0 -1
  92. package/dist/hike.js +0 -20
  93. package/dist/hike.js.map +0 -1
  94. package/src/__tests__/hike.test.ts +0 -117
  95. package/src/__tests__/job.test.ts +0 -98
  96. package/src/adapters.ts +0 -68
  97. package/src/health.ts +0 -23
  98. package/src/hike.ts +0 -77
  99. package/src/job.ts +0 -20
@@ -0,0 +1,90 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { z } from 'zod';
4
+
5
+ import { Result } from '../result';
6
+ import { trail } from '../trail';
7
+ import type { TrailInput, TrailOutput, TrailResult } from '../type-utils';
8
+ import { inputOf, outputOf } from '../type-utils';
9
+
10
+ const greetTrail = trail('greet', {
11
+ input: z.object({ name: z.string() }),
12
+ output: z.object({ message: z.string() }),
13
+ run: (input) => Result.ok({ message: `Hello, ${input.name}!` }),
14
+ });
15
+
16
+ const noOutputTrail = trail('ping', {
17
+ input: z.object({}),
18
+ run: () => Result.ok(),
19
+ });
20
+
21
+ describe('type-utils', () => {
22
+ describe('inputOf', () => {
23
+ test('returns the Zod input schema and can parse valid input', () => {
24
+ const schema = inputOf(greetTrail);
25
+ const result = schema.safeParse({ name: 'Alice' });
26
+ expect(result.success).toBe(true);
27
+ expect(
28
+ (result as { success: true; data: { name: string } }).data
29
+ ).toEqual({
30
+ name: 'Alice',
31
+ });
32
+ });
33
+
34
+ test('preserves specific schema type so .shape is accessible', () => {
35
+ const schema = inputOf(greetTrail);
36
+ // .shape is only available on z.ZodObject, not the broader z.ZodType
37
+ expect(schema.shape).toBeDefined();
38
+ expect(schema.shape.name).toBeDefined();
39
+ });
40
+ });
41
+
42
+ describe('outputOf', () => {
43
+ test('returns the Zod output schema when defined', () => {
44
+ const schema = outputOf(greetTrail);
45
+ expect(schema).toBeDefined();
46
+ // oxlint-disable-next-line no-non-null-assertion -- guarded by toBeDefined() above
47
+ const result = schema!.safeParse({ message: 'hello' });
48
+ expect(result.success).toBe(true);
49
+ });
50
+
51
+ test('returns undefined when no output schema', () => {
52
+ const schema = outputOf(noOutputTrail);
53
+ expect(schema).toBeUndefined();
54
+ });
55
+ });
56
+
57
+ describe('type-level checks', () => {
58
+ test('TrailInput matches expected shape', () => {
59
+ // If this compiles, the type is correct
60
+ const _input: TrailInput<typeof greetTrail> = { name: 'test' };
61
+ expect(_input.name).toBe('test');
62
+ });
63
+
64
+ test('TrailOutput matches expected shape', () => {
65
+ // If this compiles, the type is correct
66
+ const _output: TrailOutput<typeof greetTrail> = { message: 'hello' };
67
+ expect(_output.message).toBe('hello');
68
+ });
69
+ });
70
+
71
+ describe('TrailResult', () => {
72
+ test('extracts Result<Output, Error> from a trail', () => {
73
+ const t = trail('test.result', {
74
+ input: z.object({ q: z.string() }),
75
+ output: z.object({ answer: z.string() }),
76
+ run: (input) => Result.ok({ answer: input.q }),
77
+ });
78
+
79
+ type Expected = Result<{ answer: string }, Error>;
80
+ type Actual = TrailResult<typeof t>;
81
+
82
+ // Compile-time check: assignment works in both directions
83
+ const _check1: Expected = {} as Actual;
84
+ const _check2: Actual = {} as Expected;
85
+
86
+ // Runtime: type exists and is usable
87
+ expect(true).toBe(true);
88
+ });
89
+ });
90
+ });
@@ -3,6 +3,7 @@ import { describe, expect, test } from 'bun:test';
3
3
  import { z } from 'zod';
4
4
 
5
5
  import { Result } from '../result.js';
6
+ import { service } from '../service.js';
6
7
  import { topo } from '../topo.js';
7
8
  import type { TopoIssue } from '../validate-topo.js';
8
9
  import { validateTopo } from '../validate-topo.js';
@@ -17,6 +18,7 @@ const noop = async () => Result.ok();
17
18
  const mockTrail = (
18
19
  id: string,
19
20
  overrides?: {
21
+ follow?: readonly string[];
20
22
  examples?: readonly {
21
23
  name: string;
22
24
  input: unknown;
@@ -24,22 +26,22 @@ const mockTrail = (
24
26
  error?: string;
25
27
  }[];
26
28
  output?: z.ZodType;
29
+ services?: readonly ReturnType<typeof service>[];
27
30
  }
28
31
  ) => ({
32
+ follow: Object.freeze([...(overrides?.follow ?? [])]),
29
33
  id,
30
- implementation: noop,
31
34
  input: z.object({ name: z.string() }),
32
35
  kind: 'trail' as const,
36
+ run: noop,
37
+ services: Object.freeze([...(overrides?.services ?? [])]),
33
38
  ...overrides,
34
39
  });
35
40
 
36
- const mockHike = (id: string, follows: readonly string[]) => ({
37
- follows,
38
- id,
39
- implementation: noop,
40
- input: z.object({ q: z.string() }),
41
- kind: 'hike' as const,
42
- });
41
+ const mockService = (id: string) =>
42
+ service(id, {
43
+ create: () => Result.ok({ id }),
44
+ });
43
45
 
44
46
  const mockEvent = (id: string, from?: readonly string[]) => ({
45
47
  from,
@@ -65,7 +67,9 @@ describe('validateTopo', () => {
65
67
  test('valid topo passes', () => {
66
68
  const app = topo('app', {
67
69
  add: mockTrail('entity.add'),
68
- onboard: mockHike('entity.onboard', ['entity.add']),
70
+ onboard: mockTrail('entity.onboard', {
71
+ follow: ['entity.add'],
72
+ }),
69
73
  updated: mockEvent('entity.updated', ['entity.add']),
70
74
  });
71
75
 
@@ -73,10 +77,12 @@ describe('validateTopo', () => {
73
77
  expect(result.isOk()).toBe(true);
74
78
  });
75
79
 
76
- describe('hike follows', () => {
77
- test('hike following non-existent trail fails', () => {
80
+ describe('trail follow', () => {
81
+ test('trail following non-existent trail fails', () => {
78
82
  const app = topo('app', {
79
- onboard: mockHike('entity.onboard', ['entity.missing']),
83
+ onboard: mockTrail('entity.onboard', {
84
+ follow: ['entity.missing'],
85
+ }),
80
86
  });
81
87
 
82
88
  const result = validateTopo(app);
@@ -84,13 +90,85 @@ describe('validateTopo', () => {
84
90
 
85
91
  const issues = extractIssues(result);
86
92
  expect(issues).toHaveLength(1);
87
- expect(issues[0]?.rule).toBe('follows-exist');
93
+ expect(issues[0]?.rule).toBe('follow-exists');
88
94
  expect(issues[0]?.message).toContain('entity.missing');
89
95
  });
90
96
 
91
- test('hike following itself fails', () => {
97
+ test('trail following itself fails', () => {
98
+ const app = topo('app', {
99
+ loop: mockTrail('entity.loop', { follow: ['entity.loop'] }),
100
+ });
101
+
102
+ const result = validateTopo(app);
103
+ expect(result.isErr()).toBe(true);
104
+
105
+ const issues = extractIssues(result);
106
+ expect(issues.some((i) => i.rule === 'no-self-follow')).toBe(true);
107
+ });
108
+
109
+ test('two-node cycle (a→b→a) is detected', () => {
110
+ const app = topo('app', {
111
+ a: mockTrail('a', { follow: ['b'] }),
112
+ b: mockTrail('b', { follow: ['a'] }),
113
+ });
114
+
115
+ const result = validateTopo(app);
116
+ expect(result.isErr()).toBe(true);
117
+
118
+ const issues = extractIssues(result);
119
+ const cycleIssues = issues.filter((i) => i.rule === 'follow-cycle');
120
+ expect(cycleIssues.length).toBeGreaterThanOrEqual(1);
121
+ expect(cycleIssues[0]?.message).toContain('Cycle detected');
122
+ });
123
+
124
+ test('three-node cycle (a→b→c→a) is detected', () => {
125
+ const app = topo('app', {
126
+ a: mockTrail('a', { follow: ['b'] }),
127
+ b: mockTrail('b', { follow: ['c'] }),
128
+ c: mockTrail('c', { follow: ['a'] }),
129
+ });
130
+
131
+ const result = validateTopo(app);
132
+ expect(result.isErr()).toBe(true);
133
+
134
+ const issues = extractIssues(result);
135
+ const cycleIssues = issues.filter((i) => i.rule === 'follow-cycle');
136
+ expect(cycleIssues.length).toBeGreaterThanOrEqual(1);
137
+ expect(cycleIssues[0]?.message).toContain('Cycle detected');
138
+ });
139
+
140
+ test('valid DAG with shared targets is not flagged', () => {
141
+ const app = topo('app', {
142
+ a: mockTrail('a', { follow: ['c'] }),
143
+ b: mockTrail('b', { follow: ['c'] }),
144
+ c: mockTrail('c'),
145
+ });
146
+
147
+ const result = validateTopo(app);
148
+ expect(result.isOk()).toBe(true);
149
+ });
150
+ });
151
+
152
+ describe('service declarations', () => {
153
+ test('trail declaring a registered service passes', () => {
154
+ const db = mockService('db.main');
155
+ const app = topo('app', {
156
+ db,
157
+ show: mockTrail('entity.show', {
158
+ services: [db],
159
+ }),
160
+ });
161
+
162
+ const result = validateTopo(app);
163
+ expect(result.isOk()).toBe(true);
164
+ });
165
+
166
+ test('trail declaring a missing service fails', () => {
167
+ const db = mockService('db.main');
92
168
  const app = topo('app', {
93
- loop: mockHike('entity.loop', ['entity.loop']),
169
+ show: mockTrail('entity.show', {
170
+ services: [db],
171
+ }),
94
172
  });
95
173
 
96
174
  const result = validateTopo(app);
@@ -98,7 +176,8 @@ describe('validateTopo', () => {
98
176
 
99
177
  const issues = extractIssues(result);
100
178
  expect(issues).toHaveLength(1);
101
- expect(issues[0]?.rule).toBe('no-self-follow');
179
+ expect(issues[0]?.rule).toBe('service-exists');
180
+ expect(issues[0]?.message).toContain('db.main');
102
181
  });
103
182
  });
104
183
 
@@ -140,7 +219,7 @@ describe('validateTopo', () => {
140
219
  expect(issues[0]?.rule).toBe('output-schema-present');
141
220
  });
142
221
 
143
- test('error example with invalid input is allowed', () => {
222
+ test('ValidationError example with invalid input is allowed', () => {
144
223
  const app = topo('app', {
145
224
  show: mockTrail('entity.show', {
146
225
  examples: [
@@ -156,6 +235,44 @@ describe('validateTopo', () => {
156
235
  const result = validateTopo(app);
157
236
  expect(result.isOk()).toBe(true);
158
237
  });
238
+
239
+ test('NotFoundError example with invalid input fails', () => {
240
+ const app = topo('app', {
241
+ show: mockTrail('entity.show', {
242
+ examples: [
243
+ {
244
+ error: 'NotFoundError',
245
+ input: { name: 123 },
246
+ name: 'Not found case',
247
+ },
248
+ ],
249
+ }),
250
+ });
251
+
252
+ const result = validateTopo(app);
253
+ expect(result.isErr()).toBe(true);
254
+
255
+ const issues = extractIssues(result);
256
+ expect(issues).toHaveLength(1);
257
+ expect(issues[0]?.rule).toBe('example-input-valid');
258
+ });
259
+
260
+ test('NotFoundError example with valid input passes', () => {
261
+ const app = topo('app', {
262
+ show: mockTrail('entity.show', {
263
+ examples: [
264
+ {
265
+ error: 'NotFoundError',
266
+ input: { name: 'test' },
267
+ name: 'Not found case',
268
+ },
269
+ ],
270
+ }),
271
+ });
272
+
273
+ const result = validateTopo(app);
274
+ expect(result.isOk()).toBe(true);
275
+ });
159
276
  });
160
277
 
161
278
  describe('event origins', () => {
@@ -184,8 +301,12 @@ describe('validateTopo', () => {
184
301
  });
185
302
 
186
303
  test('collects multiple issues', () => {
304
+ const db = mockService('db.main');
187
305
  const app = topo('app', {
188
- broken: mockHike('entity.broken', ['entity.missing']),
306
+ broken: mockTrail('entity.broken', { follow: ['entity.missing'] }),
307
+ missingService: mockTrail('entity.missing-service', {
308
+ services: [db],
309
+ }),
189
310
  show: mockTrail('entity.show', {
190
311
  examples: [{ input: { name: 123 }, name: 'Bad' }],
191
312
  }),
@@ -196,6 +317,6 @@ describe('validateTopo', () => {
196
317
  expect(result.isErr()).toBe(true);
197
318
 
198
319
  const issues = extractIssues(result);
199
- expect(issues).toHaveLength(3);
320
+ expect(issues).toHaveLength(4);
200
321
  });
201
322
  });
@@ -280,4 +280,57 @@ describe('zodToJsonSchema', () => {
280
280
  expect(zodToJsonSchema(z.any())).toEqual({});
281
281
  });
282
282
  });
283
+
284
+ describe('default values', () => {
285
+ test('preserves static defaults as-is', () => {
286
+ const schema = z.string().default('hello');
287
+ expect(zodToJsonSchema(schema)).toEqual({
288
+ default: 'hello',
289
+ type: 'string',
290
+ });
291
+ });
292
+
293
+ test('omits dynamic defaults that produce different values', () => {
294
+ let counter = 0;
295
+ const schema = z.string().default(() => {
296
+ counter += 1;
297
+ return `id-${counter}`;
298
+ });
299
+ const result = zodToJsonSchema(schema);
300
+ expect(result).toEqual({ type: 'string' });
301
+ expect(result['default']).toBeUndefined();
302
+ });
303
+
304
+ test('preserves stable functional defaults that return constant values', () => {
305
+ const schema = z.string().default(() => 'constant');
306
+ const result = zodToJsonSchema(schema);
307
+ expect(result).toEqual({ default: 'constant', type: 'string' });
308
+ });
309
+
310
+ test('preserves stable functional defaults that return equivalent objects', () => {
311
+ const schema = z
312
+ .object({ key: z.string() })
313
+ .default(() => ({ key: 'val' }));
314
+ const result = zodToJsonSchema(schema);
315
+ expect(result['default']).toEqual({ key: 'val' });
316
+ });
317
+
318
+ test('preserves stable functional defaults that return equivalent arrays', () => {
319
+ const schema = z.array(z.string()).default(() => ['a', 'b']);
320
+ const result = zodToJsonSchema(schema);
321
+ expect(result['default']).toEqual(['a', 'b']);
322
+ });
323
+
324
+ test('produces identical output across repeated calls', () => {
325
+ let counter = 0;
326
+ const schema = z.string().default(() => {
327
+ counter += 1;
328
+ return `id-${counter}`;
329
+ });
330
+ const first = zodToJsonSchema(schema);
331
+ const second = zodToJsonSchema(schema);
332
+ // Key invariant: repeated calls always produce the same schema
333
+ expect(first).toEqual(second);
334
+ });
335
+ });
283
336
  });
package/src/context.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { TrailContext } from './types.js';
1
+ import { createServiceLookup } from './service.js';
2
+ import type { TrailContext, TrailContextInit } from './types.js';
3
+
4
+ type MutableTrailContext = {
5
+ -readonly [K in keyof TrailContext]: TrailContext[K];
6
+ };
2
7
 
3
8
  /**
4
9
  * Create a TrailContext with sensible defaults.
@@ -8,11 +13,15 @@ import type { TrailContext } from './types.js';
8
13
  * - All other fields come from `overrides`
9
14
  */
10
15
  export const createTrailContext = (
11
- overrides?: Partial<TrailContext>
12
- ): TrailContext => ({
13
- cwd: process.cwd(),
14
- env: process.env as Record<string, string | undefined>,
15
- requestId: Bun.randomUUIDv7(),
16
- signal: new AbortController().signal,
17
- ...overrides,
18
- });
16
+ overrides?: Partial<TrailContextInit>
17
+ ): TrailContext => {
18
+ const ctx = {
19
+ cwd: process.cwd(),
20
+ env: process.env as Record<string, string | undefined>,
21
+ requestId: Bun.randomUUIDv7(),
22
+ signal: new AbortController().signal,
23
+ ...overrides,
24
+ } as MutableTrailContext;
25
+ ctx.service = overrides?.service ?? createServiceLookup(() => ctx);
26
+ return ctx;
27
+ };
package/src/derive.ts CHANGED
@@ -14,7 +14,14 @@ import type { z } from 'zod';
14
14
  /** A surface-agnostic field descriptor derived from a Zod schema. */
15
15
  export interface Field {
16
16
  readonly name: string;
17
- readonly type: 'string' | 'number' | 'boolean' | 'enum' | 'multiselect';
17
+ readonly type:
18
+ | 'string'
19
+ | 'number'
20
+ | 'boolean'
21
+ | 'enum'
22
+ | 'multiselect'
23
+ | 'string[]'
24
+ | 'number[]';
18
25
  readonly label: string;
19
26
  readonly required: boolean;
20
27
  readonly default?: unknown | undefined;
@@ -141,7 +148,10 @@ const fieldTypeByDef: Record<string, (s: ZodInternals) => DerivedFieldType> = {
141
148
  const entries = element._zod.def['entries'] as Record<string, string>;
142
149
  return { options: Object.values(entries), type: 'multiselect' };
143
150
  }
144
- return { options: undefined, type: 'string' };
151
+ return {
152
+ options: undefined,
153
+ type: elementType === 'number' ? 'number[]' : 'string[]',
154
+ };
145
155
  },
146
156
  boolean: () => ({ options: undefined, type: 'boolean' }),
147
157
  enum: (s) => {
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Headless trail execution — the "no-surface" surface.
3
+ *
4
+ * Looks up a trail by ID in a topo, then delegates to `executeTrail`.
5
+ * Returns a `Result` and never throws.
6
+ */
7
+
8
+ import type { Topo } from './topo.js';
9
+ import { executeTrail } from './execute.js';
10
+ import type { ExecuteTrailOptions } from './execute.js';
11
+ import { NotFoundError } from './errors.js';
12
+ import { Result } from './result.js';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Options
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /** Options forwarded to `executeTrail` from `dispatch`. */
19
+ export type DispatchOptions = ExecuteTrailOptions;
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // dispatch()
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * Execute a trail by ID from a topo without mounting a surface.
27
+ *
28
+ * Resolves the trail from the topo, then runs it through the standard
29
+ * `executeTrail` pipeline. Returns `Result.err(NotFoundError)` if the
30
+ * trail ID is not registered. Never throws — unexpected exceptions are
31
+ * returned as `Result.err(InternalError)`.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const result = await dispatch(myTopo, 'greet', { name: 'Alice' });
36
+ * if (result.isOk()) console.log(result.value);
37
+ * ```
38
+ */
39
+ export const dispatch = (
40
+ topo: Topo,
41
+ id: string,
42
+ input: unknown,
43
+ options?: DispatchOptions
44
+ ): Promise<Result<unknown, Error>> => {
45
+ const trail = topo.get(id);
46
+ if (trail === undefined) {
47
+ return Promise.resolve(
48
+ Result.err(
49
+ new NotFoundError(`Trail "${id}" not found in topo "${topo.name}"`)
50
+ )
51
+ );
52
+ }
53
+ return executeTrail(trail, input, options);
54
+ };
package/src/event.ts CHANGED
@@ -11,7 +11,7 @@ import type { z } from 'zod';
11
11
  export interface EventSpec<T> {
12
12
  readonly payload: z.ZodType<T>;
13
13
  readonly description?: string | undefined;
14
- readonly markers?: Readonly<Record<string, unknown>> | undefined;
14
+ readonly metadata?: Readonly<Record<string, unknown>> | undefined;
15
15
  /** Trail IDs that produce this event (e.g. the trails it originates from). */
16
16
  readonly from?: readonly string[] | undefined;
17
17
  }
@@ -25,7 +25,7 @@ export interface Event<T> {
25
25
  readonly kind: 'event';
26
26
  readonly payload: z.ZodType<T>;
27
27
  readonly description?: string | undefined;
28
- readonly markers?: Readonly<Record<string, unknown>> | undefined;
28
+ readonly metadata?: Readonly<Record<string, unknown>> | undefined;
29
29
  /** Trail IDs that produce this event (e.g. the trails it originates from). */
30
30
  readonly from?: readonly string[] | undefined;
31
31
  }
@@ -68,7 +68,7 @@ export function event<T>(
68
68
  from: resolvedSpec.from ? Object.freeze([...resolvedSpec.from]) : undefined,
69
69
  id: resolvedId,
70
70
  kind: 'event' as const,
71
- markers: resolvedSpec.markers,
71
+ metadata: resolvedSpec.metadata,
72
72
  payload: resolvedSpec.payload,
73
73
  });
74
74
  }