@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/CHANGELOG.md +54 -0
- package/README.md +54 -17
- package/package.json +10 -9
- package/src/build.ts +1137 -78
- package/src/index.ts +18 -4
- package/src/method.ts +24 -0
- package/src/openapi.ts +357 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/blaze.d.ts +0 -25
- package/dist/blaze.d.ts.map +0 -1
- package/dist/blaze.js +0 -69
- package/dist/blaze.js.map +0 -1
- package/dist/build.d.ts +0 -54
- package/dist/build.d.ts.map +0 -1
- package/dist/build.js +0 -127
- package/dist/build.js.map +0 -1
- package/dist/hono/blaze.d.ts +0 -33
- package/dist/hono/blaze.d.ts.map +0 -1
- package/dist/hono/blaze.js +0 -232
- package/dist/hono/blaze.js.map +0 -1
- package/dist/hono/index.d.ts +0 -2
- package/dist/hono/index.d.ts.map +0 -1
- package/dist/hono/index.js +0 -2
- package/dist/hono/index.js.map +0 -1
- package/dist/hono/trailhead.d.ts +0 -36
- package/dist/hono/trailhead.d.ts.map +0 -1
- package/dist/hono/trailhead.js +0 -222
- package/dist/hono/trailhead.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +0 -1
- package/src/__tests__/build.test.ts +0 -494
- package/src/hono/__tests__/trailhead.test.ts +0 -527
- package/src/hono/index.ts +0 -1
- package/src/hono/trailhead.ts +0 -340
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
package/src/index.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
// Build (framework-agnostic)
|
|
2
2
|
export {
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
type
|
|
3
|
+
deriveHttpRoutes,
|
|
4
|
+
type DeriveHttpRoutesOptions,
|
|
5
|
+
type HttpExecutionContext,
|
|
6
|
+
type HttpHeaderSource,
|
|
7
|
+
type HttpLayerInputProjection,
|
|
6
8
|
type HttpRouteDefinition,
|
|
7
|
-
type
|
|
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
|
+
};
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc -b
|
package/.turbo/turbo-lint.log
DELETED
|
@@ -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
|
package/dist/blaze.d.ts.map
DELETED
|
@@ -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
|
package/dist/build.d.ts.map
DELETED
|
@@ -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"}
|