@ontrails/http 1.0.0-beta.12 → 1.0.0-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/build.ts CHANGED
@@ -2,19 +2,20 @@
2
2
  * Build framework-agnostic HTTP route definitions from a Trails topo.
3
3
  *
4
4
  * Each route definition describes the path, method, input source, and an
5
- * `execute` function that validates input, composes layers, and runs the
5
+ * `execute` function that validates input, composes gates, and runs the
6
6
  * implementation -- all without referencing any HTTP framework types.
7
7
  */
8
8
 
9
9
  import {
10
10
  Result,
11
- SURFACE_KEY,
11
+ TRAILHEAD_KEY,
12
12
  ValidationError,
13
13
  executeTrail,
14
+ validateEstablishedTopo,
14
15
  } from '@ontrails/core';
15
16
  import type {
16
- Layer,
17
- ServiceOverrideMap,
17
+ Gate,
18
+ ProvisionOverrideMap,
18
19
  Topo,
19
20
  Trail,
20
21
  TrailContextInit,
@@ -26,15 +27,17 @@ import type {
26
27
 
27
28
  export interface BuildHttpRoutesOptions {
28
29
  readonly basePath?: string | undefined;
29
- /** Config values for services that declare a `config` schema, keyed by service ID. */
30
+ /** Config values for provisions that declare a `config` schema, keyed by provision ID. */
30
31
  readonly configValues?:
31
32
  | Readonly<Record<string, Record<string, unknown>>>
32
33
  | undefined;
33
34
  readonly createContext?:
34
35
  | (() => TrailContextInit | Promise<TrailContextInit>)
35
36
  | undefined;
36
- readonly layers?: readonly Layer[] | undefined;
37
- readonly services?: ServiceOverrideMap | undefined;
37
+ readonly gates?: readonly Gate[] | undefined;
38
+ readonly provisions?: ProvisionOverrideMap | undefined;
39
+ /** Set to `false` to skip topo validation while building routes. */
40
+ readonly validate?: boolean | undefined;
38
41
  }
39
42
 
40
43
  export type HttpMethod = 'GET' | 'POST' | 'DELETE';
@@ -49,19 +52,19 @@ export interface HttpRouteDefinition {
49
52
  readonly inputSource: InputSource;
50
53
  readonly trail: Trail<unknown, unknown>;
51
54
  /**
52
- * Validate input, compose layers, and execute the trail implementation.
55
+ * Validate input, compose gates, and execute the trail implementation.
53
56
  *
54
57
  * The caller is responsible for parsing raw input from the request and
55
58
  * mapping the Result to an HTTP response. This function is framework-agnostic.
56
59
  *
57
- * @param signal - Optional AbortSignal from the HTTP request. When provided,
60
+ * @param abortSignal - Optional AbortSignal from the HTTP request. When provided,
58
61
  * it takes final precedence over any context factory signal, allowing
59
62
  * client-initiated cancellation to propagate into trail execution.
60
63
  */
61
64
  readonly execute: (
62
65
  input: unknown,
63
66
  requestId?: string | undefined,
64
- signal?: AbortSignal | undefined
67
+ abortSignal?: AbortSignal | undefined
65
68
  ) => Promise<Result<unknown, Error>>;
66
69
  }
67
70
 
@@ -93,15 +96,15 @@ const deriveInputSource = (method: HttpMethod): InputSource =>
93
96
 
94
97
  /** Check if a trail should be included (skip internal trails). */
95
98
  const shouldInclude = (trail: Trail<unknown, unknown>): boolean =>
96
- trail.metadata?.['internal'] !== true;
99
+ trail.meta?.['internal'] !== true;
97
100
 
98
- /** Build per-request context overrides with the HTTP surface marker. */
99
- const withHttpSurface = (
101
+ /** Build per-request context overrides with the HTTP trailhead marker. */
102
+ const withHttpTrailhead = (
100
103
  requestId: string | undefined
101
104
  ): Partial<TrailContextInit> => ({
102
105
  ...(requestId === undefined ? {} : { requestId }),
103
106
  extensions: {
104
- [SURFACE_KEY]: 'http' as const,
107
+ [TRAILHEAD_KEY]: 'http' as const,
105
108
  },
106
109
  });
107
110
 
@@ -118,17 +121,17 @@ const withHttpSurface = (
118
121
  const createExecute =
119
122
  (
120
123
  t: Trail<unknown, unknown>,
121
- layers: readonly Layer[],
124
+ gates: readonly Gate[],
122
125
  options: BuildHttpRoutesOptions
123
126
  ): HttpRouteDefinition['execute'] =>
124
- (input, requestId, signal) =>
127
+ (input, requestId, abortSignal) =>
125
128
  executeTrail(t, input, {
129
+ abortSignal,
126
130
  configValues: options.configValues,
127
131
  createContext: options.createContext,
128
- ctx: withHttpSurface(requestId),
129
- layers,
130
- services: options.services,
131
- signal,
132
+ ctx: withHttpTrailhead(requestId),
133
+ gates,
134
+ provisions: options.provisions,
132
135
  });
133
136
 
134
137
  // ---------------------------------------------------------------------------
@@ -143,13 +146,13 @@ const eligibleTrails = (app: Topo): Trail<unknown, unknown>[] =>
143
146
  const buildRoute = (
144
147
  trail: Trail<unknown, unknown>,
145
148
  basePath: string,
146
- layers: readonly Layer[],
149
+ gates: readonly Gate[],
147
150
  options: BuildHttpRoutesOptions
148
151
  ): HttpRouteDefinition => {
149
152
  const method = deriveMethod(trail);
150
153
  const path = derivePath(basePath, trail.id);
151
154
  return {
152
- execute: createExecute(trail, layers, options),
155
+ execute: createExecute(trail, gates, options),
153
156
  inputSource: deriveInputSource(method),
154
157
  method,
155
158
  path,
@@ -190,14 +193,14 @@ const registerRoute = (
190
193
  const accumulateRoutes = (
191
194
  trails: Trail<unknown, unknown>[],
192
195
  basePath: string,
193
- layers: readonly Layer[],
196
+ gates: readonly Gate[],
194
197
  options: BuildHttpRoutesOptions
195
198
  ): Result<HttpRouteDefinition[], Error> => {
196
199
  const routes: HttpRouteDefinition[] = [];
197
200
  const seenRoutes = new Map<string, string>();
198
201
 
199
202
  for (const trail of trails) {
200
- const route = buildRoute(trail, basePath, layers, options);
203
+ const route = buildRoute(trail, basePath, gates, options);
201
204
  const registered = registerRoute(route, seenRoutes, routes);
202
205
  if (registered.isErr()) {
203
206
  return registered;
@@ -218,7 +221,7 @@ const accumulateRoutes = (
218
221
  * - An HTTP method derived from intent (read -> GET, write -> POST, destroy -> DELETE)
219
222
  * - A path derived from the trail ID (dots become slashes)
220
223
  * - An input source derived from the method (GET -> query, others -> body)
221
- * - An `execute` function that validates, layers, and runs the implementation
224
+ * - An `execute` function that validates, gates, and runs the implementation
222
225
  *
223
226
  * Returns `Result.err(ValidationError)` if two trails derive the same
224
227
  * (method, path) pair. Returns `Result.ok(routes)` on success.
@@ -227,7 +230,14 @@ export const buildHttpRoutes = (
227
230
  app: Topo,
228
231
  options: BuildHttpRoutesOptions = {}
229
232
  ): Result<HttpRouteDefinition[], Error> => {
233
+ if (options.validate !== false) {
234
+ const validated = validateEstablishedTopo(app);
235
+ if (validated.isErr()) {
236
+ return Result.err(validated.error);
237
+ }
238
+ }
239
+
230
240
  const basePath = (options.basePath ?? '').replace(/\/+$/, '');
231
- const layers = options.layers ?? [];
232
- return accumulateRoutes(eligibleTrails(app), basePath, layers, options);
241
+ const gates = options.gates ?? [];
242
+ return accumulateRoutes(eligibleTrails(app), basePath, gates, options);
233
243
  };
@@ -4,55 +4,55 @@ import {
4
4
  InternalError,
5
5
  NotFoundError,
6
6
  Result,
7
- service,
7
+ provision,
8
8
  trail,
9
9
  topo,
10
10
  } from '@ontrails/core';
11
- import type { Layer } from '@ontrails/core';
11
+ import type { Gate } from '@ontrails/core';
12
12
  import { z } from 'zod';
13
13
 
14
- import { blaze } from '../blaze.js';
14
+ import { trailhead } from '../trailhead.js';
15
15
 
16
16
  // ---------------------------------------------------------------------------
17
17
  // Test trails
18
18
  // ---------------------------------------------------------------------------
19
19
 
20
20
  const echoTrail = trail('echo', {
21
+ blaze: (input) => Result.ok({ reply: input.message }),
21
22
  description: 'Echo a message back',
22
23
  input: z.object({ message: z.string() }),
23
24
  intent: 'read',
24
25
  output: z.object({ reply: z.string() }),
25
- run: (input) => Result.ok({ reply: input.message }),
26
26
  });
27
27
 
28
28
  const createTrail = trail('item.create', {
29
+ blaze: (input) => Result.ok({ id: '123', name: input.name }),
29
30
  description: 'Create an item',
30
31
  input: z.object({ name: z.string() }),
31
32
  output: z.object({ id: z.string(), name: z.string() }),
32
- run: (input) => Result.ok({ id: '123', name: input.name }),
33
33
  });
34
34
 
35
35
  const deleteTrail = trail('item.delete', {
36
+ blaze: (_input) => Result.ok({ deleted: true }),
36
37
  description: 'Delete an item',
37
38
  input: z.object({ id: z.string() }),
38
39
  intent: 'destroy',
39
- run: (_input) => Result.ok({ deleted: true }),
40
40
  });
41
41
 
42
42
  const notFoundTrail = trail('item.get', {
43
+ blaze: (_input) => Result.err(new NotFoundError('Item not found')),
43
44
  description: 'Get an item that does not exist',
44
45
  input: z.object({ id: z.string() }),
45
46
  intent: 'read',
46
- run: (_input) => Result.err(new NotFoundError('Item not found')),
47
47
  });
48
48
 
49
49
  const internalTrail = trail('crash', {
50
+ blaze: () => Result.err(new InternalError('Something broke')),
50
51
  description: 'Always fails with internal error',
51
52
  input: z.object({}),
52
- run: () => Result.err(new InternalError('Something broke')),
53
53
  });
54
54
 
55
- const dbService = service('db.main', {
55
+ const dbProvision = provision('db.main', {
56
56
  create: () =>
57
57
  Result.ok({
58
58
  source: 'factory',
@@ -65,7 +65,7 @@ const dbService = service('db.main', {
65
65
 
66
66
  /** Make a request against a Hono test app. */
67
67
  const request = (
68
- app: Awaited<ReturnType<typeof blaze>>,
68
+ app: Awaited<ReturnType<typeof trailhead>>,
69
69
  method: string,
70
70
  path: string,
71
71
  body?: Record<string, unknown>,
@@ -82,7 +82,7 @@ const request = (
82
82
 
83
83
  /** Make a request with a raw string body. */
84
84
  const requestRaw = (
85
- app: Awaited<ReturnType<typeof blaze>>,
85
+ app: Awaited<ReturnType<typeof trailhead>>,
86
86
  method: string,
87
87
  path: string,
88
88
  rawBody: string,
@@ -100,29 +100,31 @@ const requestRaw = (
100
100
  // Tests
101
101
  // ---------------------------------------------------------------------------
102
102
 
103
- describe('blaze (Hono adapter)', () => {
103
+ describe('trailhead (Hono connector)', () => {
104
104
  describe('validation', () => {
105
- test('blaze throws on invalid topo', async () => {
105
+ test('trailhead throws on invalid topo', async () => {
106
106
  const t = trail('broken', {
107
- follow: ['nonexistent.trail'],
107
+ blaze: () => Result.ok({}),
108
+ crosses: ['nonexistent.trail'],
108
109
  input: z.object({}),
109
110
  output: z.object({}),
110
- run: () => Result.ok({}),
111
111
  });
112
112
  const app = topo('test', { t });
113
- await expect(blaze(app, { serve: false })).rejects.toThrow(/validation/i);
113
+ await expect(trailhead(app, { serve: false })).rejects.toThrow(
114
+ /validation/i
115
+ );
114
116
  });
115
117
 
116
- test('blaze skips validation when validate: false', async () => {
118
+ test('trailhead skips validation when validate: false', async () => {
117
119
  const t = trail('broken', {
118
- follow: ['nonexistent.trail'],
120
+ blaze: () => Result.ok({}),
121
+ crosses: ['nonexistent.trail'],
119
122
  input: z.object({}),
120
123
  output: z.object({}),
121
- run: () => Result.ok({}),
122
124
  });
123
125
  const app = topo('test', { t });
124
126
  await expect(
125
- blaze(app, { serve: false, validate: false })
127
+ trailhead(app, { serve: false, validate: false })
126
128
  ).resolves.toBeDefined();
127
129
  });
128
130
  });
@@ -130,7 +132,7 @@ describe('blaze (Hono adapter)', () => {
130
132
  describe('GET handler', () => {
131
133
  test('returns 200 with data on success', async () => {
132
134
  const app = topo('testapp', { echoTrail });
133
- const hono = await blaze(app, { serve: false });
135
+ const hono = await trailhead(app, { serve: false });
134
136
 
135
137
  const res = await request(hono, 'GET', '/echo?message=hello');
136
138
  expect(res.status).toBe(200);
@@ -141,7 +143,7 @@ describe('blaze (Hono adapter)', () => {
141
143
 
142
144
  test('returns 400 on invalid input', async () => {
143
145
  const app = topo('testapp', { echoTrail });
144
- const hono = await blaze(app, { serve: false });
146
+ const hono = await trailhead(app, { serve: false });
145
147
 
146
148
  const res = await request(hono, 'GET', '/echo');
147
149
  expect(res.status).toBe(400);
@@ -155,7 +157,7 @@ describe('blaze (Hono adapter)', () => {
155
157
  describe('POST handler', () => {
156
158
  test('returns 200 with data on success', async () => {
157
159
  const app = topo('testapp', { createTrail });
158
- const hono = await blaze(app, { serve: false });
160
+ const hono = await trailhead(app, { serve: false });
159
161
 
160
162
  const res = await request(hono, 'POST', '/item/create', {
161
163
  name: 'Widget',
@@ -168,7 +170,7 @@ describe('blaze (Hono adapter)', () => {
168
170
 
169
171
  test('returns 400 on invalid input', async () => {
170
172
  const app = topo('testapp', { createTrail });
171
- const hono = await blaze(app, { serve: false });
173
+ const hono = await trailhead(app, { serve: false });
172
174
 
173
175
  const res = await request(hono, 'POST', '/item/create', {});
174
176
  expect(res.status).toBe(400);
@@ -179,14 +181,14 @@ describe('blaze (Hono adapter)', () => {
179
181
 
180
182
  test('POST with empty input schema succeeds without a body', async () => {
181
183
  const emptyWriteTrail = trail('empty.write', {
184
+ blaze: () => Result.ok({ ok: true }),
182
185
  input: z.object({}),
183
186
  intent: 'write',
184
187
  output: z.object({ ok: z.boolean() }),
185
- run: () => Result.ok({ ok: true }),
186
188
  });
187
189
 
188
190
  const app = topo('testapp', { emptyWriteTrail });
189
- const hono = await blaze(app, { serve: false });
191
+ const hono = await trailhead(app, { serve: false });
190
192
 
191
193
  // No body, no Content-Type header — mirrors a client that obeys the
192
194
  // OpenAPI spec (no requestBody declared for empty-input POST routes).
@@ -201,7 +203,7 @@ describe('blaze (Hono adapter)', () => {
201
203
  describe('DELETE handler', () => {
202
204
  test('returns 200 with data on success', async () => {
203
205
  const app = topo('testapp', { deleteTrail });
204
- const hono = await blaze(app, { serve: false });
206
+ const hono = await trailhead(app, { serve: false });
205
207
 
206
208
  const res = await request(hono, 'DELETE', '/item/delete', {
207
209
  id: 'abc',
@@ -216,7 +218,7 @@ describe('blaze (Hono adapter)', () => {
216
218
  describe('error mapping', () => {
217
219
  test('NotFoundError maps to 404', async () => {
218
220
  const app = topo('testapp', { notFoundTrail });
219
- const hono = await blaze(app, { serve: false });
221
+ const hono = await trailhead(app, { serve: false });
220
222
 
221
223
  const res = await request(hono, 'GET', '/item/get?id=missing');
222
224
  expect(res.status).toBe(404);
@@ -228,7 +230,7 @@ describe('blaze (Hono adapter)', () => {
228
230
 
229
231
  test('InternalError maps to 500', async () => {
230
232
  const app = topo('testapp', { internalTrail });
231
- const hono = await blaze(app, { serve: false });
233
+ const hono = await trailhead(app, { serve: false });
232
234
 
233
235
  const res = await request(hono, 'POST', '/crash', {});
234
236
  expect(res.status).toBe(500);
@@ -239,14 +241,14 @@ describe('blaze (Hono adapter)', () => {
239
241
 
240
242
  test('thrown exceptions map to 500', async () => {
241
243
  const throwTrail = trail('throw', {
242
- input: z.object({}),
243
- run: () => {
244
+ blaze: () => {
244
245
  throw new Error('unexpected crash');
245
246
  },
247
+ input: z.object({}),
246
248
  });
247
249
 
248
250
  const app = topo('testapp', { throwTrail });
249
- const hono = await blaze(app, { serve: false });
251
+ const hono = await trailhead(app, { serve: false });
250
252
 
251
253
  const res = await request(hono, 'POST', '/throw', {});
252
254
  expect(res.status).toBe(500);
@@ -256,12 +258,12 @@ describe('blaze (Hono adapter)', () => {
256
258
  });
257
259
  });
258
260
 
259
- describe('layers', () => {
260
- test('layers compose around trail execution', async () => {
261
+ describe('gates', () => {
262
+ test('gates compose around trail execution', async () => {
261
263
  const calls: string[] = [];
262
264
 
263
- const testLayer: Layer = {
264
- name: 'test-layer',
265
+ const testGate: Gate = {
266
+ name: 'test-gate',
265
267
  wrap(_trail, impl) {
266
268
  return async (input, ctx) => {
267
269
  calls.push('before');
@@ -273,7 +275,7 @@ describe('blaze (Hono adapter)', () => {
273
275
  };
274
276
 
275
277
  const app = topo('testapp', { echoTrail });
276
- const hono = await blaze(app, { layers: [testLayer], serve: false });
278
+ const hono = await trailhead(app, { gates: [testGate], serve: false });
277
279
 
278
280
  const res = await request(hono, 'GET', '/echo?message=hi');
279
281
  expect(res.status).toBe(200);
@@ -284,7 +286,7 @@ describe('blaze (Hono adapter)', () => {
284
286
  describe('malformed JSON body', () => {
285
287
  test('returns 400 for invalid JSON in POST body', async () => {
286
288
  const app = topo('testapp', { createTrail });
287
- const hono = await blaze(app, { serve: false });
289
+ const hono = await trailhead(app, { serve: false });
288
290
 
289
291
  const res = await requestRaw(hono, 'POST', '/item/create', '{invalid');
290
292
  expect(res.status).toBe(400);
@@ -297,7 +299,7 @@ describe('blaze (Hono adapter)', () => {
297
299
 
298
300
  test('returns 400 for invalid JSON in DELETE body', async () => {
299
301
  const app = topo('testapp', { deleteTrail });
300
- const hono = await blaze(app, { serve: false });
302
+ const hono = await trailhead(app, { serve: false });
301
303
 
302
304
  const res = await requestRaw(hono, 'DELETE', '/item/delete', 'not-json');
303
305
  expect(res.status).toBe(400);
@@ -310,13 +312,13 @@ describe('blaze (Hono adapter)', () => {
310
312
  describe('query param parsing', () => {
311
313
  test('numeric-looking string is preserved as string', async () => {
312
314
  const stringIdTrail = trail('lookup', {
315
+ blaze: (input) => Result.ok({ id: input.id }),
313
316
  input: z.object({ id: z.string() }),
314
317
  intent: 'read',
315
- run: (input) => Result.ok({ id: input.id }),
316
318
  });
317
319
 
318
320
  const app = topo('testapp', { stringIdTrail });
319
- const hono = await blaze(app, { serve: false });
321
+ const hono = await trailhead(app, { serve: false });
320
322
 
321
323
  const res = await request(hono, 'GET', '/lookup?id=00123');
322
324
  expect(res.status).toBe(200);
@@ -327,13 +329,13 @@ describe('blaze (Hono adapter)', () => {
327
329
 
328
330
  test('repeated keys become arrays', async () => {
329
331
  const tagsTrail = trail('tags', {
332
+ blaze: (input) => Result.ok({ tags: input.tags }),
330
333
  input: z.object({ tags: z.array(z.string()) }),
331
334
  intent: 'read',
332
- run: (input) => Result.ok({ tags: input.tags }),
333
335
  });
334
336
 
335
337
  const app = topo('testapp', { tagsTrail });
336
- const hono = await blaze(app, { serve: false });
338
+ const hono = await trailhead(app, { serve: false });
337
339
 
338
340
  const res = await request(hono, 'GET', '/tags?tags=a&tags=b');
339
341
  expect(res.status).toBe(200);
@@ -344,13 +346,13 @@ describe('blaze (Hono adapter)', () => {
344
346
 
345
347
  test('single value is wrapped in array when schema expects z.array()', async () => {
346
348
  const tagsTrail = trail('tags.single', {
349
+ blaze: (input) => Result.ok({ tags: input.tags }),
347
350
  input: z.object({ tags: z.array(z.string()) }),
348
351
  intent: 'read',
349
- run: (input) => Result.ok({ tags: input.tags }),
350
352
  });
351
353
 
352
354
  const app = topo('testapp', { tagsTrail });
353
- const hono = await blaze(app, { serve: false });
355
+ const hono = await trailhead(app, { serve: false });
354
356
 
355
357
  const res = await request(hono, 'GET', '/tags/single?tags=foo');
356
358
  expect(res.status).toBe(200);
@@ -361,13 +363,13 @@ describe('blaze (Hono adapter)', () => {
361
363
 
362
364
  test('single value stays scalar when schema expects a string', async () => {
363
365
  const nameTrail = trail('name.check', {
366
+ blaze: (input) => Result.ok({ name: input.name }),
364
367
  input: z.object({ name: z.string() }),
365
368
  intent: 'read',
366
- run: (input) => Result.ok({ name: input.name }),
367
369
  });
368
370
 
369
371
  const app = topo('testapp', { nameTrail });
370
- const hono = await blaze(app, { serve: false });
372
+ const hono = await trailhead(app, { serve: false });
371
373
 
372
374
  const res = await request(hono, 'GET', '/name/check?name=bar');
373
375
  expect(res.status).toBe(200);
@@ -378,13 +380,13 @@ describe('blaze (Hono adapter)', () => {
378
380
 
379
381
  test('optional array field with single value is wrapped in array', async () => {
380
382
  const optArrayTrail = trail('opt.array', {
383
+ blaze: (input) => Result.ok({ ids: input.ids }),
381
384
  input: z.object({ ids: z.array(z.string()).optional() }),
382
385
  intent: 'read',
383
- run: (input) => Result.ok({ ids: input.ids }),
384
386
  });
385
387
 
386
388
  const app = topo('testapp', { optArrayTrail });
387
- const hono = await blaze(app, { serve: false });
389
+ const hono = await trailhead(app, { serve: false });
388
390
 
389
391
  const res = await request(hono, 'GET', '/opt/array?ids=one');
390
392
  expect(res.status).toBe(200);
@@ -399,17 +401,17 @@ describe('blaze (Hono adapter)', () => {
399
401
  let capturedSignal: AbortSignal | undefined;
400
402
 
401
403
  const signalTrail = trail('signal.check', {
404
+ blaze: (_input, ctx) => {
405
+ capturedSignal = ctx.abortSignal;
406
+ return Result.ok({ ok: true });
407
+ },
402
408
  input: z.object({}),
403
409
  intent: 'read',
404
410
  output: z.object({ ok: z.boolean() }),
405
- run: (_input, ctx) => {
406
- capturedSignal = ctx.signal;
407
- return Result.ok({ ok: true });
408
- },
409
411
  });
410
412
 
411
413
  const app = topo('testapp', { signalTrail });
412
- const hono = await blaze(app, { serve: false });
414
+ const hono = await trailhead(app, { serve: false });
413
415
 
414
416
  const res = await request(hono, 'GET', '/signal/check');
415
417
  expect(res.status).toBe(200);
@@ -420,17 +422,17 @@ describe('blaze (Hono adapter)', () => {
420
422
  let capturedSignal: AbortSignal | undefined;
421
423
 
422
424
  const signalTrail = trail('signal.aborted', {
425
+ blaze: (_input, ctx) => {
426
+ capturedSignal = ctx.abortSignal;
427
+ return Result.ok({ ok: true });
428
+ },
423
429
  input: z.object({}),
424
430
  intent: 'read',
425
431
  output: z.object({ ok: z.boolean() }),
426
- run: (_input, ctx) => {
427
- capturedSignal = ctx.signal;
428
- return Result.ok({ ok: true });
429
- },
430
432
  });
431
433
 
432
434
  const app = topo('testapp', { signalTrail });
433
- const hono = await blaze(app, { serve: false });
435
+ const hono = await trailhead(app, { serve: false });
434
436
 
435
437
  const controller = new AbortController();
436
438
  controller.abort();
@@ -453,16 +455,16 @@ describe('blaze (Hono adapter)', () => {
453
455
  let capturedRequestId: string | undefined;
454
456
 
455
457
  const ctxTrail = trail('ctx.check', {
456
- input: z.object({}),
457
- intent: 'read',
458
- run: (_input, ctx) => {
458
+ blaze: (_input, ctx) => {
459
459
  capturedRequestId = ctx.requestId;
460
460
  return Result.ok({ ok: true });
461
461
  },
462
+ input: z.object({}),
463
+ intent: 'read',
462
464
  });
463
465
 
464
466
  const app = topo('testapp', { ctxTrail });
465
- const hono = await blaze(app, { serve: false });
467
+ const hono = await trailhead(app, { serve: false });
466
468
 
467
469
  const res = await request(hono, 'GET', '/ctx/check', undefined, {
468
470
  'X-Request-ID': 'custom-req-123',
@@ -476,20 +478,20 @@ describe('blaze (Hono adapter)', () => {
476
478
  let contextUsed = false;
477
479
 
478
480
  const ctxTrail = trail('ctx.custom', {
479
- input: z.object({}),
480
- intent: 'read',
481
- run: (_input, ctx) => {
481
+ blaze: (_input, ctx) => {
482
482
  contextUsed = ctx.extensions?.['custom'] === true;
483
483
  return Result.ok({ ok: true });
484
484
  },
485
+ input: z.object({}),
486
+ intent: 'read',
485
487
  });
486
488
 
487
489
  const app = topo('testapp', { ctxTrail });
488
- const hono = await blaze(app, {
490
+ const hono = await trailhead(app, {
489
491
  createContext: () => ({
492
+ abortSignal: new AbortController().signal,
490
493
  extensions: { custom: true },
491
494
  requestId: 'test-id',
492
- signal: new AbortController().signal,
493
495
  }),
494
496
  serve: false,
495
497
  });
@@ -499,23 +501,23 @@ describe('blaze (Hono adapter)', () => {
499
501
  expect(contextUsed).toBe(true);
500
502
  });
501
503
 
502
- test('service overrides reach the trail through blaze()', async () => {
503
- const serviceTrail = trail('service.check', {
504
+ test('provision overrides reach the trail through trailhead()', async () => {
505
+ const provisionTrail = trail('provision.check', {
506
+ blaze: (_input, ctx) =>
507
+ Result.ok({ source: dbProvision.from(ctx).source as string }),
504
508
  input: z.object({}),
505
509
  intent: 'read',
506
510
  output: z.object({ source: z.string() }),
507
- run: (_input, ctx) =>
508
- Result.ok({ source: dbService.from(ctx).source as string }),
509
- services: [dbService],
511
+ provisions: [dbProvision],
510
512
  });
511
513
 
512
- const app = topo('testapp', { dbService, serviceTrail });
513
- const hono = await blaze(app, {
514
+ const app = topo('testapp', { dbProvision, provisionTrail });
515
+ const hono = await trailhead(app, {
516
+ provisions: { 'db.main': { source: 'override' } },
514
517
  serve: false,
515
- services: { 'db.main': { source: 'override' } },
516
518
  });
517
519
 
518
- const res = await request(hono, 'GET', '/service/check');
520
+ const res = await request(hono, 'GET', '/provision/check');
519
521
  expect(res.status).toBe(200);
520
522
 
521
523
  const json = await res.json();
package/src/hono/index.ts CHANGED
@@ -1 +1 @@
1
- export { blaze, type BlazeHttpOptions } from './blaze.js';
1
+ export { trailhead, type TrailheadHttpOptions } from './trailhead.js';