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

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/index.ts CHANGED
@@ -1,8 +1,22 @@
1
1
  // Build (framework-agnostic)
2
2
  export {
3
- buildHttpRoutes,
4
- type BuildHttpRoutesOptions,
5
- type HttpMethod,
3
+ deriveHttpRoutes,
4
+ type DeriveHttpRoutesOptions,
5
+ type HttpExecutionContext,
6
+ type HttpHeaderSource,
7
+ type HttpLayerInputProjection,
6
8
  type HttpRouteDefinition,
7
- type InputSource,
9
+ type ResolveHttpPermit,
10
+ type ResolveHttpPermitInput,
8
11
  } from './build.js';
12
+ export {
13
+ deriveHttpInputSource,
14
+ deriveHttpMethod,
15
+ deriveHttpOperationMethod,
16
+ httpMethodByIntent,
17
+ } from './method.js';
18
+ export type { HttpMethod, HttpOperationMethod, InputSource } from './method.js';
19
+
20
+ // OpenAPI
21
+ export { deriveOpenApiSpec } from './openapi.js';
22
+ export type { OpenApiOptions, OpenApiSpec, OpenApiServer } from './openapi.js';
package/src/method.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { Intent, WebhookMethod } from '@ontrails/core';
2
+
3
+ export type HttpMethod = 'GET' | 'POST' | 'DELETE' | WebhookMethod;
4
+
5
+ export type HttpOperationMethod = Lowercase<HttpMethod>;
6
+
7
+ export type InputSource = 'query' | 'body' | 'webhook';
8
+
9
+ export const httpMethodByIntent = {
10
+ destroy: 'DELETE',
11
+ read: 'GET',
12
+ write: 'POST',
13
+ } as const satisfies Record<Intent, HttpMethod>;
14
+
15
+ export const deriveHttpMethod = (intent: Intent): HttpMethod =>
16
+ (httpMethodByIntent as Partial<Record<string, HttpMethod>>)[intent] ?? 'POST';
17
+
18
+ export const deriveHttpOperationMethod = (
19
+ intent: Intent
20
+ ): HttpOperationMethod =>
21
+ deriveHttpMethod(intent).toLowerCase() as HttpOperationMethod;
22
+
23
+ export const deriveHttpInputSource = (method: HttpMethod): InputSource =>
24
+ method === 'GET' ? 'query' : 'body';
package/src/openapi.ts ADDED
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Derive an OpenAPI 3.1 specification from a Topo.
3
+ *
4
+ * Converts each trail into an HTTP operation, deriving paths, methods,
5
+ * parameters, and response schemas from the trail contract.
6
+ */
7
+
8
+ import {
9
+ ValidationError,
10
+ filterSurfaceTrails,
11
+ projectErrorClassSurface,
12
+ validateSurfaceTopo,
13
+ zodToJsonSchema,
14
+ } from '@ontrails/core';
15
+ import type {
16
+ SurfaceSelectionOptions,
17
+ SurfaceValidationOptions,
18
+ Topo,
19
+ Trail,
20
+ } from '@ontrails/core';
21
+
22
+ import { deriveHttpOperationMethod } from './method.js';
23
+
24
+ type JsonSchema = Readonly<Record<string, unknown>>;
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Public types
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export interface OpenApiServer {
31
+ readonly url: string;
32
+ readonly description?: string | undefined;
33
+ }
34
+
35
+ export interface OpenApiOptions
36
+ extends SurfaceSelectionOptions, SurfaceValidationOptions {
37
+ /** Default: `graph.name` */
38
+ readonly title?: string | undefined;
39
+ /** Default: `'1.0.0'` */
40
+ readonly version?: string | undefined;
41
+ readonly description?: string | undefined;
42
+ readonly servers?: readonly OpenApiServer[] | undefined;
43
+ /** Prefix for all paths. Default: `''` */
44
+ readonly basePath?: string | undefined;
45
+ }
46
+
47
+ /** Minimal OpenAPI 3.1 spec shape — intentionally plain objects, no heavy library. */
48
+ export interface OpenApiSpec {
49
+ readonly openapi: '3.1.0';
50
+ readonly info: {
51
+ readonly title: string;
52
+ readonly version: string;
53
+ readonly description?: string | undefined;
54
+ };
55
+ readonly servers?: readonly OpenApiServer[] | undefined;
56
+ readonly paths: Record<string, Record<string, unknown>>;
57
+ readonly components: { readonly schemas: Record<string, unknown> };
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Helpers
62
+ // ---------------------------------------------------------------------------
63
+
64
+ /** `entity.show` → `/entity/show` */
65
+ const trailIdToPath = (id: string, basePath: string): string =>
66
+ `${basePath}/${id.split('.').join('/')}`;
67
+
68
+ /** First segment of a dotted ID, used as an OpenAPI tag. */
69
+ const tagFromId = (id: string): string => id.split('.')[0] ?? id;
70
+
71
+ /** Convert a Zod schema to JSON Schema via the core helper. */
72
+ const toJsonSchema = (schema: unknown): JsonSchema =>
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ zodToJsonSchema(schema as any) as JsonSchema;
75
+
76
+ /** Build query parameters from a JSON Schema `properties` object. */
77
+ const buildQueryParameters = (
78
+ jsonSchema: JsonSchema
79
+ ): Record<string, unknown>[] => {
80
+ const properties = jsonSchema['properties'] as
81
+ | Record<string, Record<string, unknown>>
82
+ | undefined;
83
+ if (!properties) {
84
+ return [];
85
+ }
86
+
87
+ const required = new Set(
88
+ Array.isArray(jsonSchema['required'])
89
+ ? (jsonSchema['required'] as string[])
90
+ : []
91
+ );
92
+
93
+ return Object.entries(properties).map(([name, schema]) => ({
94
+ in: 'query',
95
+ name,
96
+ required: required.has(name),
97
+ schema,
98
+ }));
99
+ };
100
+
101
+ /** Map a single error example to a status code entry, or undefined if not mappable. */
102
+ const errorExampleToEntry = (
103
+ errorName: string,
104
+ seen: Set<number>
105
+ ): [string, { description: string }] | undefined => {
106
+ const projection = projectErrorClassSurface('http', errorName);
107
+ if (projection === undefined) {
108
+ return undefined;
109
+ }
110
+
111
+ const { code } = projection;
112
+ if (seen.has(code)) {
113
+ return undefined;
114
+ }
115
+ seen.add(code);
116
+ return [String(code), { description: errorName }];
117
+ };
118
+
119
+ /** Extract error status codes from trail examples that have an `error` field. */
120
+ const errorResponsesFromExamples = (
121
+ examples: readonly { error?: string | undefined }[]
122
+ ): Record<string, { description: string }> => {
123
+ const responses: Record<string, { description: string }> = {};
124
+ const seen = new Set<number>();
125
+
126
+ for (const ex of examples) {
127
+ if (!ex.error) {
128
+ continue;
129
+ }
130
+ const entry = errorExampleToEntry(ex.error, seen);
131
+ if (entry) {
132
+ const [code, value] = entry;
133
+ responses[code] = value;
134
+ }
135
+ }
136
+
137
+ return responses;
138
+ };
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Operation builder — split into focused helpers
142
+ // ---------------------------------------------------------------------------
143
+
144
+ /** True when the body is required — non-object schemas are always required,
145
+ * object schemas are required only when they have at least one required property. */
146
+ const isBodyRequired = (schema: JsonSchema): boolean => {
147
+ if (schema['type'] !== 'object') {
148
+ return true;
149
+ }
150
+ return (
151
+ Array.isArray(schema['required']) &&
152
+ (schema['required'] as unknown[]).length > 0
153
+ );
154
+ };
155
+
156
+ /** Build the input portion of an operation (parameters or requestBody). */
157
+ const buildInputSpec = (
158
+ t: Trail<unknown, unknown, unknown>,
159
+ method: string
160
+ ): Record<string, unknown> => {
161
+ if (!t.input) {
162
+ return {};
163
+ }
164
+ const inputSchema = toJsonSchema(t.input);
165
+
166
+ if (method === 'get') {
167
+ const params = buildQueryParameters(inputSchema);
168
+ return params.length > 0 ? { parameters: params } : {};
169
+ }
170
+
171
+ const properties = inputSchema['properties'] as
172
+ | Record<string, unknown>
173
+ | undefined;
174
+ if (properties !== undefined && Object.keys(properties).length === 0) {
175
+ return {};
176
+ }
177
+
178
+ return {
179
+ requestBody: {
180
+ content: { 'application/json': { schema: inputSchema } },
181
+ required: isBodyRequired(inputSchema),
182
+ },
183
+ };
184
+ };
185
+
186
+ /** Wrap a raw output schema in the `{ data: ... }` envelope the HTTP adapter uses. */
187
+ const wrapInDataEnvelope = (outputSchema: JsonSchema): JsonSchema => ({
188
+ properties: { data: outputSchema },
189
+ required: ['data'],
190
+ type: 'object',
191
+ });
192
+
193
+ /** Shared error response body schema: `{ error: { message, code, category } }`. */
194
+ const errorResponseSchema: JsonSchema = {
195
+ properties: {
196
+ error: {
197
+ properties: {
198
+ category: { type: 'string' },
199
+ code: { type: 'string' },
200
+ message: { type: 'string' },
201
+ },
202
+ required: ['message', 'code', 'category'],
203
+ type: 'object',
204
+ },
205
+ },
206
+ required: ['error'],
207
+ type: 'object',
208
+ };
209
+
210
+ /** Build the 200 response entry. */
211
+ const buildSuccessResponse = (
212
+ t: Trail<unknown, unknown, unknown>
213
+ ): Record<string, unknown> => {
214
+ if (!t.output) {
215
+ return { '200': { description: 'Success' } };
216
+ }
217
+ const outputSchema = toJsonSchema(t.output);
218
+ return {
219
+ '200': {
220
+ content: {
221
+ 'application/json': { schema: wrapInDataEnvelope(outputSchema) },
222
+ },
223
+ description: 'Success',
224
+ },
225
+ };
226
+ };
227
+
228
+ /** Build the default 400 validation error response. */
229
+ const validationErrorResponse: Record<
230
+ string,
231
+ { content: Record<string, unknown>; description: string }
232
+ > = {
233
+ '400': {
234
+ content: { 'application/json': { schema: errorResponseSchema } },
235
+ description: 'Validation error',
236
+ },
237
+ };
238
+
239
+ /** Build all responses (success + error) for a trail. */
240
+ const buildResponses = (
241
+ t: Trail<unknown, unknown, unknown>
242
+ ): Record<string, unknown> => {
243
+ const examples = (t.examples ?? []) as readonly {
244
+ error?: string | undefined;
245
+ }[];
246
+ return {
247
+ ...buildSuccessResponse(t),
248
+ ...errorResponsesFromExamples(examples),
249
+ ...validationErrorResponse,
250
+ };
251
+ };
252
+
253
+ const operationIdFromTrailId = (trailId: string): string =>
254
+ trailId.replaceAll('.', '_');
255
+
256
+ /** Build a complete OpenAPI operation for a trail. */
257
+ const buildOperation = (
258
+ t: Trail<unknown, unknown, unknown>,
259
+ method: string
260
+ ): Record<string, unknown> => ({
261
+ operationId: operationIdFromTrailId(t.id),
262
+ responses: buildResponses(t),
263
+ tags: [tagFromId(t.id)],
264
+ ...(t.description ? { summary: t.description } : {}),
265
+ ...buildInputSpec(t, method),
266
+ });
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // Path collection
270
+ // ---------------------------------------------------------------------------
271
+
272
+ /** Collect all paths from public trails in the graph. */
273
+ const collectPaths = (
274
+ graph: Topo,
275
+ basePath: string,
276
+ options?: OpenApiOptions
277
+ ): Record<string, Record<string, unknown>> => {
278
+ const paths: Record<string, Record<string, unknown>> = {};
279
+ const seenRoutes = new Map<string, string>();
280
+ const seenOperationIds = new Map<string, string>();
281
+
282
+ for (const t of filterSurfaceTrails(graph.list(), {
283
+ exclude: options?.exclude,
284
+ include: options?.include,
285
+ intent: options?.intent,
286
+ })) {
287
+ const method = deriveHttpOperationMethod(t.intent);
288
+ const path = trailIdToPath(t.id, basePath);
289
+ const routeKey = `${method.toUpperCase()} ${path}`;
290
+ const existingId = seenRoutes.get(routeKey);
291
+ if (existingId !== undefined) {
292
+ throw new ValidationError(
293
+ `HTTP route collision: trails "${existingId}" and "${t.id}" both derive ${routeKey}`
294
+ );
295
+ }
296
+ seenRoutes.set(routeKey, t.id);
297
+
298
+ const operationId = operationIdFromTrailId(t.id);
299
+ const existingOperationId = seenOperationIds.get(operationId);
300
+ if (existingOperationId !== undefined) {
301
+ throw new ValidationError(
302
+ `OpenAPI operationId collision: trails "${existingOperationId}" and "${t.id}" both derive operationId "${operationId}"`
303
+ );
304
+ }
305
+ seenOperationIds.set(operationId, t.id);
306
+
307
+ paths[path] ??= {};
308
+ paths[path][method] = buildOperation(t, method);
309
+ }
310
+
311
+ return paths;
312
+ };
313
+
314
+ /** Build the info object from options and graph name. */
315
+ const buildInfo = (
316
+ graphName: string,
317
+ options?: OpenApiOptions
318
+ ): OpenApiSpec['info'] => ({
319
+ title: options?.title ?? graphName,
320
+ version: options?.version ?? '1.0.0',
321
+ ...(options?.description ? { description: options.description } : {}),
322
+ });
323
+
324
+ // ---------------------------------------------------------------------------
325
+ // Public API
326
+ // ---------------------------------------------------------------------------
327
+
328
+ /**
329
+ * Derive an OpenAPI 3.1 specification from a Topo.
330
+ *
331
+ * Iterates all trails, skipping signals and internal trails, and produces
332
+ * paths, operations, parameters, and response schemas derived from
333
+ * the trail contract.
334
+ */
335
+ export const deriveOpenApiSpec = (
336
+ graph: Topo,
337
+ options?: OpenApiOptions
338
+ ): OpenApiSpec => {
339
+ const validated = validateSurfaceTopo(graph, options);
340
+ if (validated.isErr()) {
341
+ throw validated.error;
342
+ }
343
+
344
+ return {
345
+ components: { schemas: {} },
346
+ info: buildInfo(graph.name, options),
347
+ openapi: '3.1.0',
348
+ paths: collectPaths(
349
+ graph,
350
+ (options?.basePath ?? '').replace(/\/+$/, ''),
351
+ options
352
+ ),
353
+ ...(options?.servers && options.servers.length > 0
354
+ ? { servers: options.servers }
355
+ : {}),
356
+ };
357
+ };
@@ -1 +0,0 @@
1
- $ tsc -b
@@ -1,3 +0,0 @@
1
- $ oxlint ./src
2
- Found 0 warnings and 0 errors.
3
- Finished in 13ms on 6 files with 93 rules using 24 threads.
@@ -1 +0,0 @@
1
- $ tsc --noEmit
package/dist/blaze.d.ts DELETED
@@ -1,25 +0,0 @@
1
- /**
2
- * blaze() -- the one-liner HTTP server launcher.
3
- *
4
- * ```ts
5
- * const app = topo("myapp", entity);
6
- * await blaze(app, { port: 3000 });
7
- * ```
8
- */
9
- import type { Layer, Topo, TrailContext } from '@ontrails/core';
10
- import { Hono } from 'hono';
11
- export interface BlazeHttpOptions {
12
- readonly basePath?: string | undefined;
13
- readonly createContext?: (() => TrailContext | Promise<TrailContext>) | undefined;
14
- readonly hostname?: string | undefined;
15
- readonly layers?: readonly Layer[] | undefined;
16
- readonly name?: string | undefined;
17
- readonly port?: number | undefined;
18
- /** Set false to return the Hono app without starting a server. */
19
- readonly serve?: boolean | undefined;
20
- }
21
- /**
22
- * Build HTTP routes from a topo, create a Hono app, and optionally start serving.
23
- */
24
- export declare const blaze: (app: Topo, options?: BlazeHttpOptions) => Promise<Hono>;
25
- //# sourceMappingURL=blaze.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"blaze.d.ts","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,GAC5C,SAAS,CAAC;IACd,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACtC;AA8DD;;GAEG;AAEH,eAAO,MAAM,KAAK,GAChB,KAAK,IAAI,EACT,UAAS,gBAAqB,KAC7B,OAAO,CAAC,IAAI,CAed,CAAC"}
package/dist/blaze.js DELETED
@@ -1,69 +0,0 @@
1
- /**
2
- * blaze() -- the one-liner HTTP server launcher.
3
- *
4
- * ```ts
5
- * const app = topo("myapp", entity);
6
- * await blaze(app, { port: 3000 });
7
- * ```
8
- */
9
- import { Hono } from 'hono';
10
- import { buildHttpRoutes } from './build.js';
11
- // ---------------------------------------------------------------------------
12
- // Internal: register routes on Hono app
13
- // ---------------------------------------------------------------------------
14
- /** Route registration keyed by HTTP method. */
15
- const routeRegistrars = {
16
- DELETE: (hono, route) => {
17
- hono.delete(route.path, route.handler);
18
- },
19
- GET: (hono, route) => {
20
- hono.get(route.path, route.handler);
21
- },
22
- POST: (hono, route) => {
23
- hono.post(route.path, route.handler);
24
- },
25
- };
26
- const registerRoutes = (hono, app, options) => {
27
- const routes = buildHttpRoutes(app, {
28
- basePath: options.basePath,
29
- createContext: options.createContext,
30
- layers: options.layers,
31
- });
32
- for (const route of routes) {
33
- routeRegistrars[route.method](hono, route);
34
- }
35
- };
36
- // ---------------------------------------------------------------------------
37
- // Global error handler
38
- // ---------------------------------------------------------------------------
39
- const registerErrorHandler = (hono) => {
40
- // oxlint-disable-next-line prefer-await-to-callbacks -- Hono's onError API requires a callback
41
- hono.onError((err, c) => c.json({
42
- error: {
43
- category: 'internal',
44
- code: 'InternalError',
45
- message: err.message,
46
- },
47
- }, 500));
48
- };
49
- // ---------------------------------------------------------------------------
50
- // blaze
51
- // ---------------------------------------------------------------------------
52
- /**
53
- * Build HTTP routes from a topo, create a Hono app, and optionally start serving.
54
- */
55
- // oxlint-disable-next-line require-await -- async for consistency with other blaze() surfaces
56
- export const blaze = async (app, options = {}) => {
57
- const hono = new Hono();
58
- registerErrorHandler(hono);
59
- registerRoutes(hono, app, options);
60
- if (options.serve !== false) {
61
- Bun.serve({
62
- fetch: hono.fetch,
63
- hostname: options.hostname ?? '0.0.0.0',
64
- port: options.port ?? 3000,
65
- });
66
- }
67
- return hono;
68
- };
69
- //# sourceMappingURL=blaze.js.map
package/dist/blaze.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"blaze.js","sourceRoot":"","sources":["../src/blaze.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAmB7C,8EAA8E;AAC9E,wCAAwC;AACxC,8EAA8E;AAE9E,+CAA+C;AAC/C,MAAM,eAAe,GAGjB;IACF,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACtB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IACD,GAAG,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACnB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;CACF,CAAC;AAEF,MAAM,cAAc,GAAG,CACrB,IAAU,EACV,GAAS,EACT,OAAyB,EACnB,EAAE;IACR,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,EAAE;QAClC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IAEH,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC,CAAC;AAEF,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E,MAAM,oBAAoB,GAAG,CAAC,IAAU,EAAQ,EAAE;IAChD,+FAA+F;IAC/F,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CACtB,CAAC,CAAC,IAAI,CACJ;QACE,KAAK,EAAE;YACL,QAAQ,EAAE,UAAU;YACpB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB;KACF,EACD,GAAG,CACJ,CACF,CAAC;AACJ,CAAC,CAAC;AAEF,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E;;GAEG;AACH,8FAA8F;AAC9F,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,EACxB,GAAS,EACT,UAA4B,EAAE,EACf,EAAE;IACjB,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IAExB,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC3B,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAEnC,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QAC5B,GAAG,CAAC,KAAK,CAAC;YACR,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,SAAS;YACvC,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
package/dist/build.d.ts DELETED
@@ -1,54 +0,0 @@
1
- /**
2
- * Build framework-agnostic HTTP route definitions from a Trails topo.
3
- *
4
- * Each route definition describes the path, method, input source, and an
5
- * `execute` function that validates input, composes gates, and runs the
6
- * implementation -- all without referencing any HTTP framework types.
7
- */
8
- import { Result } from '@ontrails/core';
9
- import type { Gate, ProvisionOverrideMap, Topo, Trail, TrailContextInit } from '@ontrails/core';
10
- export interface BuildHttpRoutesOptions {
11
- readonly basePath?: string | undefined;
12
- /** Config values for provisions that declare a `config` schema, keyed by provision ID. */
13
- readonly configValues?: Readonly<Record<string, Record<string, unknown>>> | undefined;
14
- readonly createContext?: (() => TrailContextInit | Promise<TrailContextInit>) | undefined;
15
- readonly gates?: readonly Gate[] | undefined;
16
- readonly provisions?: ProvisionOverrideMap | undefined;
17
- /** Set to `false` to skip topo validation while building routes. */
18
- readonly validate?: boolean | undefined;
19
- }
20
- export type HttpMethod = 'GET' | 'POST' | 'DELETE';
21
- /** Input source derived from the HTTP method. */
22
- export type InputSource = 'query' | 'body';
23
- export interface HttpRouteDefinition {
24
- readonly method: HttpMethod;
25
- readonly path: string;
26
- readonly trailId: string;
27
- readonly inputSource: InputSource;
28
- readonly trail: Trail<unknown, unknown>;
29
- /**
30
- * Validate input, compose gates, and execute the trail implementation.
31
- *
32
- * The caller is responsible for parsing raw input from the request and
33
- * mapping the Result to an HTTP response. This function is framework-agnostic.
34
- *
35
- * @param abortSignal - Optional AbortSignal from the HTTP request. When provided,
36
- * it takes final precedence over any context factory signal, allowing
37
- * client-initiated cancellation to propagate into trail execution.
38
- */
39
- readonly execute: (input: unknown, requestId?: string | undefined, abortSignal?: AbortSignal | undefined) => Promise<Result<unknown, Error>>;
40
- }
41
- /**
42
- * Build HTTP route definitions from a topo.
43
- *
44
- * Each trail becomes an HttpRouteDefinition with:
45
- * - An HTTP method derived from intent (read -> GET, write -> POST, destroy -> DELETE)
46
- * - A path derived from the trail ID (dots become slashes)
47
- * - An input source derived from the method (GET -> query, others -> body)
48
- * - An `execute` function that validates, gates, and runs the implementation
49
- *
50
- * Returns `Result.err(ValidationError)` if two trails derive the same
51
- * (method, path) pair. Returns `Result.ok(routes)` on success.
52
- */
53
- export declare const buildHttpRoutes: (app: Topo, options?: BuildHttpRoutesOptions) => Result<HttpRouteDefinition[], Error>;
54
- //# sourceMappingURL=build.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,MAAM,EAKP,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,IAAI,EACJ,oBAAoB,EACpB,IAAI,EACJ,KAAK,EACL,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAMxB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,0FAA0F;IAC1F,QAAQ,CAAC,YAAY,CAAC,EAClB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GACjD,SAAS,CAAC;IACd,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,GACpD,SAAS,CAAC;IACd,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,IAAI,EAAE,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACvD,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAED,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEnD,iDAAiD;AACjD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACxC;;;;;;;;;OASG;IACH,QAAQ,CAAC,OAAO,EAAE,CAChB,KAAK,EAAE,OAAO,EACd,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,EAC9B,WAAW,CAAC,EAAE,WAAW,GAAG,SAAS,KAClC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;CACtC;AAoJD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC1B,KAAK,IAAI,EACT,UAAS,sBAA2B,KACnC,MAAM,CAAC,mBAAmB,EAAE,EAAE,KAAK,CAWrC,CAAC"}