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

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 +67 -30
  3. package/README.md +19 -19
  4. package/dist/context.d.ts +1 -1
  5. package/dist/context.d.ts.map +1 -1
  6. package/dist/context.js +5 -4
  7. package/dist/context.js.map +1 -1
  8. package/dist/derive.d.ts +8 -3
  9. package/dist/derive.d.ts.map +1 -1
  10. package/dist/derive.js +7 -7
  11. package/dist/derive.js.map +1 -1
  12. package/dist/event.d.ts +4 -41
  13. package/dist/event.d.ts.map +1 -1
  14. package/dist/event.js +4 -14
  15. package/dist/event.js.map +1 -1
  16. package/dist/execute.d.ts +11 -9
  17. package/dist/execute.d.ts.map +1 -1
  18. package/dist/execute.js +34 -151
  19. package/dist/execute.js.map +1 -1
  20. package/dist/gate.d.ts +17 -0
  21. package/dist/gate.d.ts.map +1 -0
  22. package/dist/gate.js +21 -0
  23. package/dist/gate.js.map +1 -0
  24. package/dist/index.d.ts +10 -7
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +9 -7
  27. package/dist/index.js.map +1 -1
  28. package/dist/layer.d.ts +5 -16
  29. package/dist/layer.d.ts.map +1 -1
  30. package/dist/layer.js +3 -20
  31. package/dist/layer.js.map +1 -1
  32. package/dist/provision-config.d.ts +22 -0
  33. package/dist/provision-config.d.ts.map +1 -0
  34. package/dist/provision-config.js +210 -0
  35. package/dist/provision-config.js.map +1 -0
  36. package/dist/provision.d.ts +71 -0
  37. package/dist/provision.d.ts.map +1 -0
  38. package/dist/provision.js +56 -0
  39. package/dist/provision.js.map +1 -0
  40. package/dist/run.d.ts +27 -0
  41. package/dist/run.d.ts.map +1 -0
  42. package/dist/run.js +34 -0
  43. package/dist/run.js.map +1 -0
  44. package/dist/service-config.d.ts +22 -0
  45. package/dist/service-config.d.ts.map +1 -0
  46. package/dist/service-config.js +210 -0
  47. package/dist/service-config.js.map +1 -0
  48. package/dist/service.d.ts +39 -37
  49. package/dist/service.d.ts.map +1 -1
  50. package/dist/service.js +21 -21
  51. package/dist/service.js.map +1 -1
  52. package/dist/signal.d.ts +33 -0
  53. package/dist/signal.d.ts.map +1 -0
  54. package/dist/signal.js +17 -0
  55. package/dist/signal.js.map +1 -0
  56. package/dist/topo.d.ts +10 -10
  57. package/dist/topo.d.ts.map +1 -1
  58. package/dist/topo.js +48 -35
  59. package/dist/topo.js.map +1 -1
  60. package/dist/trail.d.ts +19 -17
  61. package/dist/trail.d.ts.map +1 -1
  62. package/dist/trail.js +5 -4
  63. package/dist/trail.js.map +1 -1
  64. package/dist/types.d.ts +26 -9
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/types.js +2 -1
  67. package/dist/types.js.map +1 -1
  68. package/dist/validate-topo.d.ts +2 -2
  69. package/dist/validate-topo.js +31 -31
  70. package/dist/validate-topo.js.map +1 -1
  71. package/package.json +1 -1
  72. package/src/__tests__/context.test.ts +8 -8
  73. package/src/__tests__/execute.test.ts +124 -122
  74. package/src/__tests__/{layer.test.ts → gate.test.ts} +26 -26
  75. package/src/__tests__/{dispatch.test.ts → run.test.ts} +39 -39
  76. package/src/__tests__/service-config.test.ts +228 -0
  77. package/src/__tests__/service.test.ts +71 -71
  78. package/src/__tests__/{event.test.ts → signal.test.ts} +15 -15
  79. package/src/__tests__/topo.test.ts +54 -54
  80. package/src/__tests__/trail-permit.test.ts +60 -0
  81. package/src/__tests__/trail.test.ts +58 -58
  82. package/src/__tests__/type-utils.test.ts +3 -3
  83. package/src/__tests__/validate-topo.test.ts +38 -38
  84. package/src/context.ts +5 -4
  85. package/src/derive.ts +8 -8
  86. package/src/event.ts +11 -73
  87. package/src/execute.ts +59 -238
  88. package/src/{layer.ts → gate.ts} +13 -13
  89. package/src/index.ts +26 -21
  90. package/src/provision-config.ts +373 -0
  91. package/src/provision.ts +148 -0
  92. package/src/{dispatch.ts → run.ts} +8 -8
  93. package/src/signal.ts +65 -0
  94. package/src/topo.ts +74 -52
  95. package/src/trail.ts +30 -23
  96. package/src/types.ts +30 -10
  97. package/src/validate-topo.ts +33 -33
  98. package/tsconfig.tsbuildinfo +1 -1
  99. package/src/service.ts +0 -139
@@ -6,9 +6,9 @@ import { z } from 'zod';
6
6
  import { InternalError, ValidationError } from '../errors';
7
7
  import { executeTrail } from '../execute';
8
8
  import { createTrailContext } from '../context';
9
- import type { Layer } from '../layer';
9
+ import type { Gate } from '../gate';
10
10
  import { Result } from '../result';
11
- import { service } from '../service';
11
+ import { provision } from '../provision';
12
12
  import { trail } from '../trail';
13
13
  import type { TrailContext, TrailContextInit } from '../types';
14
14
 
@@ -17,84 +17,84 @@ import type { TrailContext, TrailContextInit } from '../types';
17
17
  // ---------------------------------------------------------------------------
18
18
 
19
19
  const echoTrail = trail('echo', {
20
+ blaze: (input) => Result.ok({ value: input.value }),
20
21
  input: z.object({ value: z.string() }),
21
22
  output: z.object({ value: z.string() }),
22
- run: (input) => Result.ok({ value: input.value }),
23
23
  });
24
24
 
25
25
  const failingTrail = trail('fails', {
26
+ blaze: () => Result.err(new ValidationError('bad input')),
26
27
  input: z.object({}),
27
- run: () => Result.err(new ValidationError('bad input')),
28
28
  });
29
29
 
30
30
  const throwingTrail = trail('throws', {
31
- input: z.object({}),
32
- run: () => {
31
+ blaze: () => {
33
32
  throw new Error('kaboom');
34
33
  },
34
+ input: z.object({}),
35
35
  });
36
36
 
37
- const nextServiceId = (name: string): string =>
38
- `test.service.${name}.${Bun.randomUUIDv7()}`;
37
+ const nextProvisionId = (name: string): string =>
38
+ `test.provision.${name}.${Bun.randomUUIDv7()}`;
39
39
 
40
- const createResolvedValueService = (
40
+ const createResolvedValueProvision = (
41
41
  id: string,
42
42
  onCreate: () => void,
43
43
  value: number
44
44
  ) =>
45
- service(id, {
45
+ provision(id, {
46
46
  create: () => {
47
47
  onCreate();
48
48
  return Result.ok({ value });
49
49
  },
50
50
  });
51
51
 
52
- const createEagerServiceTrail = (
52
+ const createEagerProvisionTrail = (
53
53
  id: string,
54
- counter: ReturnType<typeof createResolvedValueService>,
54
+ counter: ReturnType<typeof createResolvedValueProvision>,
55
55
  onRun: (value: number) => void
56
56
  ) =>
57
- trail('service.eager', {
58
- input: z.object({}),
59
- output: z.object({ total: z.number() }),
60
- run: (_input, ctx) => {
61
- const fromAccessor = ctx.service<{ value: number }>(id);
57
+ trail('provision.eager', {
58
+ blaze: (_input, ctx) => {
59
+ const fromAccessor = ctx.provision<{ value: number }>(id);
62
60
  const fromDefinition = counter.from(ctx);
63
61
  onRun(fromDefinition.value);
64
62
  return Result.ok({ total: fromAccessor.value + 1 });
65
63
  },
66
- services: [counter],
64
+ input: z.object({}),
65
+ output: z.object({ total: z.number() }),
66
+ provisions: [counter],
67
67
  });
68
68
 
69
- const createServiceProbeLayer = (
70
- counter: ReturnType<typeof createResolvedValueService>,
69
+ const createProvisionProbeGate = (
70
+ counter: ReturnType<typeof createResolvedValueProvision>,
71
71
  onResolve: (value: number) => void
72
- ): Layer => ({
73
- name: 'uses-service',
72
+ ): Gate => ({
73
+ name: 'uses-provision',
74
74
  wrap(_trail, impl) {
75
75
  return async (input, ctx) => {
76
- onResolve(ctx.service<{ value: number }>(counter).value);
76
+ onResolve(ctx.provision<{ value: number }>(counter).value);
77
77
  return await impl(input, ctx);
78
78
  };
79
79
  },
80
80
  });
81
81
 
82
- const createSingletonService = (id: string, onCreate: () => number) =>
83
- service(id, {
82
+ const createSingletonProvision = (id: string, onCreate: () => number) =>
83
+ provision(id, {
84
84
  create: () => Result.ok({ createdAtCall: onCreate() }),
85
85
  });
86
86
 
87
87
  const createSingletonTrail = (
88
- singleton: ReturnType<typeof createSingletonService>
88
+ singleton: ReturnType<typeof createSingletonProvision>
89
89
  ) =>
90
- trail('service.singleton', {
91
- input: z.object({}),
92
- output: z.object({ createdAtCall: z.number() }),
93
- run: (_input, ctx) =>
90
+ trail('provision.singleton', {
91
+ blaze: (_input, ctx) =>
94
92
  Result.ok({
95
93
  createdAtCall: singleton.from(ctx).createdAtCall,
96
94
  }),
97
- services: [singleton],
95
+ input: z.object({}),
96
+ output: z.object({ createdAtCall: z.number() }),
97
+ provisions: [singleton],
98
98
  });
99
99
 
100
100
  const unwrapExecution = async (
@@ -117,60 +117,60 @@ describe('executeTrail', () => {
117
117
  expect(result.unwrap()).toEqual({ value: 'hello' });
118
118
  });
119
119
 
120
- test('eagerly resolves declared services before layers and implementation run', async () => {
121
- const id = nextServiceId('eager');
120
+ test('eagerly resolves declared provisions before gates and implementation run', async () => {
121
+ const id = nextProvisionId('eager');
122
122
  const captures = {
123
123
  createCalls: 0,
124
- layerResolved: undefined as number | undefined,
124
+ gateResolved: undefined as number | undefined,
125
125
  runResolved: undefined as number | undefined,
126
126
  };
127
- const counter = createResolvedValueService(
127
+ const counter = createResolvedValueProvision(
128
128
  id,
129
129
  () => {
130
130
  captures.createCalls += 1;
131
131
  },
132
132
  41
133
133
  );
134
- const layeredTrail = createEagerServiceTrail(id, counter, (value) => {
134
+ const layeredTrail = createEagerProvisionTrail(id, counter, (value) => {
135
135
  captures.runResolved = value;
136
136
  });
137
- const layer = createServiceProbeLayer(counter, (value) => {
138
- captures.layerResolved = value;
137
+ const gate = createProvisionProbeGate(counter, (value) => {
138
+ captures.gateResolved = value;
139
139
  });
140
140
 
141
- const result = await executeTrail(layeredTrail, {}, { layers: [layer] });
141
+ const result = await executeTrail(layeredTrail, {}, { gates: [gate] });
142
142
 
143
143
  expect(result.unwrap()).toEqual({ total: 42 });
144
144
  expect(captures.createCalls).toBe(1);
145
- expect(captures.layerResolved).toBe(41);
145
+ expect(captures.gateResolved).toBe(41);
146
146
  expect(captures.runResolved).toBe(41);
147
147
  });
148
148
 
149
- test('awaits async service factories before running the trail', async () => {
150
- const db = service(nextServiceId('async-factory'), {
149
+ test('awaits async provision factories before running the trail', async () => {
150
+ const db = provision(nextProvisionId('async-factory'), {
151
151
  create: async () => {
152
152
  await Bun.sleep(0);
153
153
  return Result.ok({ source: 'async-factory' });
154
154
  },
155
155
  });
156
- const serviceTrail = trail('service.async-factory', {
156
+ const provisionTrail = trail('provision.async-factory', {
157
+ blaze: (_input, ctx) =>
158
+ Result.ok({ source: db.from(ctx).source as string }),
157
159
  input: z.object({}),
158
160
  output: z.object({ source: z.string() }),
159
- run: (_input, ctx) =>
160
- Result.ok({ source: db.from(ctx).source as string }),
161
- services: [db],
161
+ provisions: [db],
162
162
  });
163
163
 
164
- const result = await executeTrail(serviceTrail, {});
164
+ const result = await executeTrail(provisionTrail, {});
165
165
 
166
166
  expect(result.isOk()).toBe(true);
167
167
  expect(result.unwrap()).toEqual({ source: 'async-factory' });
168
168
  });
169
169
 
170
- test('reuses cached singleton services across executions', async () => {
171
- const id = nextServiceId('singleton');
170
+ test('reuses cached singleton provisions across executions', async () => {
171
+ const id = nextProvisionId('singleton');
172
172
  const captures = { createCalls: 0 };
173
- const singleton = createSingletonService(id, () => {
173
+ const singleton = createSingletonProvision(id, () => {
174
174
  captures.createCalls += 1;
175
175
  return captures.createCalls;
176
176
  });
@@ -185,17 +185,17 @@ describe('executeTrail', () => {
185
185
  expect(captures.createCalls).toBe(1);
186
186
  });
187
187
 
188
- test('scopes cached singleton services to compatible service contexts', async () => {
189
- const id = nextServiceId('singleton-context');
190
- const envAwareService = service(id, {
188
+ test('scopes cached singleton provisions to compatible provision contexts', async () => {
189
+ const id = nextProvisionId('singleton-context');
190
+ const envAwareProvision = provision(id, {
191
191
  create: (ctx) => Result.ok({ value: String(ctx.env?.VAL) }),
192
192
  });
193
- const envAwareTrail = trail('service.singleton-context', {
193
+ const envAwareTrail = trail('provision.singleton-context', {
194
+ blaze: (_input, ctx) =>
195
+ Result.ok({ value: envAwareProvision.from(ctx).value }),
194
196
  input: z.object({}),
195
197
  output: z.object({ value: z.string() }),
196
- run: (_input, ctx) =>
197
- Result.ok({ value: envAwareService.from(ctx).value }),
198
- services: [envAwareService],
198
+ provisions: [envAwareProvision],
199
199
  });
200
200
 
201
201
  const first = await executeTrail(
@@ -233,11 +233,11 @@ describe('executeTrail', () => {
233
233
  });
234
234
  });
235
235
 
236
- describe('layers', () => {
237
- test('composes layers around execution', async () => {
236
+ describe('gates', () => {
237
+ test('composes gates around execution', async () => {
238
238
  const log: string[] = [];
239
- const layer: Layer = {
240
- name: 'test-layer',
239
+ const gate: Gate = {
240
+ name: 'test-gate',
241
241
  wrap(_trail, impl) {
242
242
  return async (input, ctx) => {
243
243
  log.push('before');
@@ -251,7 +251,7 @@ describe('executeTrail', () => {
251
251
  const result = await executeTrail(
252
252
  echoTrail,
253
253
  { value: 'x' },
254
- { layers: [layer] }
254
+ { gates: [gate] }
255
255
  );
256
256
 
257
257
  expect(result.isOk()).toBe(true);
@@ -263,11 +263,11 @@ describe('executeTrail', () => {
263
263
  test('accepts context overrides', async () => {
264
264
  let capturedCtx: TrailContext | undefined;
265
265
  const ctxTrail = trail('ctx-test', {
266
- input: z.object({}),
267
- run: (_input, ctx) => {
266
+ blaze: (_input, ctx) => {
268
267
  capturedCtx = ctx;
269
268
  return Result.ok(null);
270
269
  },
270
+ input: z.object({}),
271
271
  });
272
272
 
273
273
  await executeTrail(ctxTrail, {}, { ctx: { requestId: 'override-id' } });
@@ -275,36 +275,36 @@ describe('executeTrail', () => {
275
275
  expect(capturedCtx?.requestId).toBe('override-id');
276
276
  });
277
277
 
278
- test('accepts signal override', async () => {
278
+ test('accepts abortSignal override', async () => {
279
279
  let capturedSignal: AbortSignal | undefined;
280
280
  const sigTrail = trail('sig-test', {
281
- input: z.object({}),
282
- run: (_input, ctx) => {
283
- capturedSignal = ctx.signal;
281
+ blaze: (_input, ctx) => {
282
+ capturedSignal = ctx.abortSignal;
284
283
  return Result.ok(null);
285
284
  },
285
+ input: z.object({}),
286
286
  });
287
287
 
288
- const signal = AbortSignal.timeout(9999);
289
- await executeTrail(sigTrail, {}, { signal });
288
+ const abortSignal = AbortSignal.timeout(9999);
289
+ await executeTrail(sigTrail, {}, { abortSignal });
290
290
 
291
- expect(capturedSignal).toBe(signal);
291
+ expect(capturedSignal).toBe(abortSignal);
292
292
  });
293
293
 
294
294
  test('accepts context factory', async () => {
295
295
  let capturedCtx: TrailContext | undefined;
296
296
  const ctxTrail = trail('factory-test', {
297
- input: z.object({}),
298
- run: (_input, ctx) => {
297
+ blaze: (_input, ctx) => {
299
298
  capturedCtx = ctx;
300
299
  return Result.ok(null);
301
300
  },
301
+ input: z.object({}),
302
302
  });
303
303
 
304
304
  const customCtx: TrailContextInit = {
305
+ abortSignal: new AbortController().signal,
305
306
  cwd: '/custom',
306
307
  requestId: 'factory-id',
307
- signal: new AbortController().signal,
308
308
  };
309
309
 
310
310
  await executeTrail(ctxTrail, {}, { createContext: () => customCtx });
@@ -316,17 +316,17 @@ describe('executeTrail', () => {
316
316
  test('context factory + ctx overrides merge correctly', async () => {
317
317
  let capturedCtx: TrailContext | undefined;
318
318
  const ctxTrail = trail('merge-test', {
319
- input: z.object({}),
320
- run: (_input, ctx) => {
319
+ blaze: (_input, ctx) => {
321
320
  capturedCtx = ctx;
322
321
  return Result.ok(null);
323
322
  },
323
+ input: z.object({}),
324
324
  });
325
325
 
326
326
  const baseCtx: TrailContextInit = {
327
+ abortSignal: new AbortController().signal,
327
328
  cwd: '/factory',
328
329
  requestId: 'factory-id',
329
- signal: new AbortController().signal,
330
330
  };
331
331
 
332
332
  await executeTrail(
@@ -345,12 +345,12 @@ describe('executeTrail', () => {
345
345
  test('deep-merges extensions from factory and overrides', async () => {
346
346
  let captured: TrailContext | undefined;
347
347
  const t = trail('ext.test', {
348
- input: z.object({}),
349
- output: z.object({}),
350
- run: (_input, ctx) => {
348
+ blaze: (_input, ctx) => {
351
349
  captured = ctx;
352
350
  return Result.ok({});
353
351
  },
352
+ input: z.object({}),
353
+ output: z.object({}),
354
354
  });
355
355
  await executeTrail(
356
356
  t,
@@ -364,23 +364,23 @@ describe('executeTrail', () => {
364
364
  expect(captured?.extensions).toEqual({ store: 'db', userId: '123' });
365
365
  });
366
366
 
367
- test('rebinds ctx.service after merging extension overrides from createContext', async () => {
367
+ test('rebinds ctx.provision after merging extension overrides from createContext', async () => {
368
368
  let resolvedSource: string | undefined;
369
- const db = service(nextServiceId('context-override'), {
369
+ const db = provision(nextProvisionId('context-override'), {
370
370
  create: () => Result.ok({ source: 'factory' }),
371
371
  });
372
- const serviceTrail = trail('ctx.service.override', {
373
- input: z.object({}),
374
- output: z.object({ source: z.string() }),
375
- run: (_input, ctx) => {
376
- resolvedSource = ctx.service<{ source: string }>(db).source;
372
+ const provisionTrail = trail('ctx.provision.override', {
373
+ blaze: (_input, ctx) => {
374
+ resolvedSource = ctx.provision<{ source: string }>(db).source;
377
375
  return Result.ok({ source: resolvedSource });
378
376
  },
379
- services: [db],
377
+ input: z.object({}),
378
+ output: z.object({ source: z.string() }),
379
+ provisions: [db],
380
380
  });
381
381
 
382
382
  const result = await executeTrail(
383
- serviceTrail,
383
+ provisionTrail,
384
384
  {},
385
385
  {
386
386
  createContext: () =>
@@ -401,17 +401,17 @@ describe('executeTrail', () => {
401
401
  expect(resolvedSource).toBe('override-context');
402
402
  });
403
403
 
404
- test('context factory seeds the service accessor when omitted', async () => {
404
+ test('context factory seeds the provision accessor when omitted', async () => {
405
405
  let capturedCtx: TrailContext | undefined;
406
- const id = nextServiceId('factory-seed');
406
+ const id = nextProvisionId('factory-seed');
407
407
  const widget = { id: 'widget-1' };
408
- const widgetTrail = trail('service.factory-seed', {
409
- input: z.object({}),
410
- run: (_input, ctx) => {
408
+ const widgetTrail = trail('provision.factory-seed', {
409
+ blaze: (_input, ctx) => {
411
410
  capturedCtx = ctx;
412
411
  return Result.ok(null);
413
412
  },
414
- services: [],
413
+ input: z.object({}),
414
+ provisions: [],
415
415
  });
416
416
 
417
417
  await executeTrail(
@@ -419,14 +419,14 @@ describe('executeTrail', () => {
419
419
  {},
420
420
  {
421
421
  createContext: () => ({
422
+ abortSignal: new AbortController().signal,
422
423
  extensions: { [id]: widget },
423
- requestId: 'seeded-service',
424
- signal: new AbortController().signal,
424
+ requestId: 'seeded-provision',
425
425
  }),
426
426
  }
427
427
  );
428
428
 
429
- expect(capturedCtx?.service(id)).toBe(widget);
429
+ expect(capturedCtx?.provision(id)).toBe(widget);
430
430
  });
431
431
  });
432
432
 
@@ -447,22 +447,22 @@ describe('executeTrail', () => {
447
447
  expect(result.error.message).toBe('kaboom');
448
448
  });
449
449
 
450
- test('short-circuits when a service factory returns Result.err', async () => {
451
- const failingService = service(nextServiceId('factory-error'), {
450
+ test('short-circuits when a provision factory returns Result.err', async () => {
451
+ const failingProvision = provision(nextProvisionId('factory-error'), {
452
452
  create: () =>
453
453
  Result.err(new ValidationError('DATABASE_URL is required')),
454
454
  });
455
455
  let ran = false;
456
- const serviceTrail = trail('service.factory-error', {
457
- input: z.object({}),
458
- run: () => {
456
+ const provisionTrail = trail('provision.factory-error', {
457
+ blaze: () => {
459
458
  ran = true;
460
459
  return Result.ok(null);
461
460
  },
462
- services: [failingService],
461
+ input: z.object({}),
462
+ provisions: [failingProvision],
463
463
  });
464
464
 
465
- const result = await executeTrail(serviceTrail, {});
465
+ const result = await executeTrail(provisionTrail, {});
466
466
 
467
467
  expect(result.isErr()).toBe(true);
468
468
  expect(result.error).toBeInstanceOf(ValidationError);
@@ -470,48 +470,50 @@ describe('executeTrail', () => {
470
470
  expect(ran).toBe(false);
471
471
  });
472
472
 
473
- test('wraps thrown service factory exceptions with the service ID in context', async () => {
474
- const explodingService = service(nextServiceId('factory-throw'), {
473
+ test('wraps thrown provision factory exceptions with the provision ID in context', async () => {
474
+ const explodingProvision = provision(nextProvisionId('factory-throw'), {
475
475
  create: () => {
476
476
  throw new Error('boom');
477
477
  },
478
478
  });
479
- const serviceTrail = trail('service.factory-throw', {
479
+ const provisionTrail = trail('provision.factory-throw', {
480
+ blaze: () => Result.ok(null),
480
481
  input: z.object({}),
481
- run: () => Result.ok(null),
482
- services: [explodingService],
482
+ provisions: [explodingProvision],
483
483
  });
484
484
 
485
- const result = await executeTrail(serviceTrail, {});
485
+ const result = await executeTrail(provisionTrail, {});
486
486
 
487
487
  expect(result.isErr()).toBe(true);
488
488
  expect(result.error).toBeInstanceOf(InternalError);
489
- expect(result.error.message).toContain(explodingService.id);
490
- expect(result.error.context).toEqual({ serviceId: explodingService.id });
489
+ expect(result.error.message).toContain(explodingProvision.id);
490
+ expect(result.error.context).toEqual({
491
+ provisionId: explodingProvision.id,
492
+ });
491
493
  });
492
494
 
493
- test('prefers explicit service overrides over cached or created instances', async () => {
494
- const id = nextServiceId('override');
495
+ test('prefers explicit provision overrides over cached or created instances', async () => {
496
+ const id = nextProvisionId('override');
495
497
  let createCalls = 0;
496
- const db = service(id, {
498
+ const db = provision(id, {
497
499
  create: () => {
498
500
  createCalls += 1;
499
501
  return Result.ok({ source: 'factory' });
500
502
  },
501
503
  });
502
- const serviceTrail = trail('service.override', {
504
+ const provisionTrail = trail('provision.override', {
505
+ blaze: (_input, ctx) =>
506
+ Result.ok({ source: db.from(ctx).source as string }),
503
507
  input: z.object({}),
504
508
  output: z.object({ source: z.string() }),
505
- run: (_input, ctx) =>
506
- Result.ok({ source: db.from(ctx).source as string }),
507
- services: [db],
509
+ provisions: [db],
508
510
  });
509
511
 
510
512
  const result = await executeTrail(
511
- serviceTrail,
513
+ provisionTrail,
512
514
  {},
513
515
  {
514
- services: { [id]: { source: 'override' } },
516
+ provisions: { [id]: { source: 'override' } },
515
517
  }
516
518
  );
517
519
 
@@ -1,34 +1,34 @@
1
- /* oxlint-disable require-await -- layer wrappers satisfy async interfaces without awaiting */
1
+ /* oxlint-disable require-await -- gate wrappers satisfy async interfaces without awaiting */
2
2
  import { describe, test, expect } from 'bun:test';
3
3
 
4
4
  import { z } from 'zod';
5
5
 
6
6
  import { createTrailContext } from '../context';
7
- import { composeLayers } from '../layer';
8
- import type { Layer } from '../layer';
7
+ import { composeGates } from '../gate';
8
+ import type { Gate } from '../gate';
9
9
  import { Result } from '../result';
10
10
  import { trail } from '../trail';
11
11
  import type { TrailContext } from '../types';
12
12
 
13
13
  const stubCtx: TrailContext = createTrailContext({
14
- requestId: 'test-layer',
15
- signal: AbortSignal.timeout(5000),
14
+ abortSignal: AbortSignal.timeout(5000),
15
+ requestId: 'test-gate',
16
16
  });
17
17
 
18
18
  const echoTrail = trail('echo', {
19
+ blaze: (input) => Result.ok({ value: input.value }),
19
20
  input: z.object({ value: z.string() }),
20
- metadata: { domain: 'test' },
21
+ meta: { domain: 'test' },
21
22
  output: z.object({ value: z.string() }),
22
- run: (input) => Result.ok({ value: input.value }),
23
23
  });
24
24
 
25
25
  // ---------------------------------------------------------------------------
26
- // Layer tests
26
+ // Gate tests
27
27
  // ---------------------------------------------------------------------------
28
28
 
29
- describe('Layer', () => {
30
- test('single layer wraps implementation', async () => {
31
- const prefixLayer: Layer = {
29
+ describe('Gate', () => {
30
+ test('single gate wraps implementation', async () => {
31
+ const prefixGate: Gate = {
32
32
  name: 'prefix',
33
33
  wrap(_trail, impl) {
34
34
  return async (input, ctx) => {
@@ -41,17 +41,17 @@ describe('Layer', () => {
41
41
  },
42
42
  };
43
43
 
44
- const wrapped = composeLayers([prefixLayer], echoTrail, echoTrail.run);
44
+ const wrapped = composeGates([prefixGate], echoTrail, echoTrail.blaze);
45
45
  const result = await wrapped({ value: 'hello' }, stubCtx);
46
46
 
47
47
  expect(result.isOk()).toBe(true);
48
48
  expect(result.unwrap()).toEqual({ value: 'prefixed:hello' });
49
49
  });
50
50
 
51
- test('multiple layers compose in outermost-first order', async () => {
51
+ test('multiple gates compose in outermost-first order', async () => {
52
52
  const log: string[] = [];
53
53
 
54
- const outer: Layer = {
54
+ const outer: Gate = {
55
55
  name: 'outer',
56
56
  wrap(_trail, impl) {
57
57
  return async (input, ctx) => {
@@ -63,7 +63,7 @@ describe('Layer', () => {
63
63
  },
64
64
  };
65
65
 
66
- const inner: Layer = {
66
+ const inner: Gate = {
67
67
  name: 'inner',
68
68
  wrap(_trail, impl) {
69
69
  return async (input, ctx) => {
@@ -75,7 +75,7 @@ describe('Layer', () => {
75
75
  },
76
76
  };
77
77
 
78
- const wrapped = composeLayers([outer, inner], echoTrail, echoTrail.run);
78
+ const wrapped = composeGates([outer, inner], echoTrail, echoTrail.blaze);
79
79
  await wrapped({ value: 'x' }, stubCtx);
80
80
 
81
81
  expect(log).toEqual([
@@ -86,15 +86,15 @@ describe('Layer', () => {
86
86
  ]);
87
87
  });
88
88
 
89
- test('layer can short-circuit without calling inner implementation', async () => {
90
- const shortCircuit: Layer = {
89
+ test('gate can short-circuit without calling inner implementation', async () => {
90
+ const shortCircuit: Gate = {
91
91
  name: 'short-circuit',
92
92
  wrap() {
93
93
  return async () => Result.err(new Error('blocked'));
94
94
  },
95
95
  };
96
96
 
97
- const wrapped = composeLayers([shortCircuit], echoTrail, echoTrail.run);
97
+ const wrapped = composeGates([shortCircuit], echoTrail, echoTrail.blaze);
98
98
  const result = await wrapped({ value: 'hello' }, stubCtx);
99
99
 
100
100
  expect(result.isErr()).toBe(true);
@@ -102,24 +102,24 @@ describe('Layer', () => {
102
102
  expect(err.error.message).toBe('blocked');
103
103
  });
104
104
 
105
- test('layer can inspect trail metadata', () => {
105
+ test('gate can inspect trail meta', () => {
106
106
  let capturedDomain: unknown;
107
107
 
108
- const inspectLayer: Layer = {
108
+ const inspectGate: Gate = {
109
109
  name: 'inspect',
110
110
  wrap(t, impl) {
111
- capturedDomain = t.metadata?.['domain'];
111
+ capturedDomain = t.meta?.['domain'];
112
112
  return impl;
113
113
  },
114
114
  };
115
115
 
116
- composeLayers([inspectLayer], echoTrail, echoTrail.run);
116
+ composeGates([inspectGate], echoTrail, echoTrail.blaze);
117
117
 
118
118
  expect(capturedDomain).toBe('test');
119
119
  });
120
120
 
121
- test('empty layers array returns implementation unchanged', () => {
122
- const wrapped = composeLayers([], echoTrail, echoTrail.run);
123
- expect(wrapped).toBe(echoTrail.run);
121
+ test('empty gates array returns implementation unchanged', () => {
122
+ const wrapped = composeGates([], echoTrail, echoTrail.blaze);
123
+ expect(wrapped).toBe(echoTrail.blaze);
124
124
  });
125
125
  });