@ontrails/http 1.0.0-beta.12

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.
@@ -0,0 +1 @@
1
+ $ tsc -b
@@ -0,0 +1,3 @@
1
+ $ oxlint ./src
2
+ Found 0 warnings and 0 errors.
3
+ Finished in 39ms on 6 files with 93 rules using 24 threads.
@@ -0,0 +1 @@
1
+ $ tsc --noEmit
package/CHANGELOG.md ADDED
@@ -0,0 +1,114 @@
1
+ # @ontrails/http
2
+
3
+ ## 1.0.0-beta.12
4
+
5
+ ### Patch Changes
6
+
7
+ - Complete trifecta for config, permits, and crumbs (formerly tracks)
8
+
9
+ - **config**: Add `configService`, `config.layer`, `config.trail`, and `config.workspace` trails with full `defineConfig`, `resolve`, `describe`, `explain`, `doctor`, and code generation support
10
+ - **permits**: Add `authService` and `auth.verify` trail for runtime authorization checks
11
+ - **crumbs**: Rename tracks to crumbs; add `crumbsService` and `crumbs.status` trail for structured event tracking
12
+ - **cli**: Fix build flag handling and improve bootstrap scaffolding
13
+ - **testing**: Expand test context helpers and example-based testing utilities
14
+ - **core/mcp/http**: Internal alignment for service and composition updates
15
+
16
+ - Updated dependencies
17
+ - @ontrails/core@1.0.0-beta.12
18
+
19
+ ## 1.0.0-beta.11
20
+
21
+ ### Patch Changes
22
+
23
+ - Add services as a first-class primitive.
24
+
25
+ Services make infrastructure dependencies declarative, injectable, and governable. Define a service with `service()`, declare it on a trail with `services: [db]`, and access it with `db.from(ctx)` or `ctx.service()`.
26
+
27
+ **Core:** `service()` factory, `ServiceSpec<T>`, `ServiceContext`, singleton resolution in `executeTrail`, in-flight creation dedup, `isService` guard, `findDuplicateServiceId`, topo service discovery and validation, `services` field on trail specs.
28
+
29
+ **Testing:** Auto-resolution of `mock` factories in `testAll`, `testExamples`, `testContracts`, and `testFollows`. Explicit `services` overrides with correct precedence (`explicit > ctx.extensions > auto-mock`). Service mock propagation through follow graphs.
30
+
31
+ **Warden:** `service-declarations` rule validates `db.from(ctx)` and `ctx.service()` usage matches declared `services: [...]`. `service-exists` rule validates declared service IDs resolve in project context. Scope-aware AST walking skips nested function boundaries.
32
+
33
+ **Surfaces:** Service overrides thread through `dispatch` and `blaze` on CLI, MCP, and HTTP.
34
+
35
+ **Introspection:** Survey and surface map outputs include service graph. Topo exposes `.services`, `.getService()`, `.hasService()`, `.listServices()`, `.serviceIds()`, `.serviceCount`.
36
+
37
+ **Docs:** ADR-009 accepted. Unified services guide, updated vocabulary, getting-started, architecture, and package READMEs.
38
+
39
+ - Updated dependencies
40
+ - @ontrails/core@1.0.0-beta.11
41
+
42
+ ## 1.0.0-beta.10
43
+
44
+ ### Patch Changes
45
+
46
+ - Cleanup and hardening pass across all packages.
47
+
48
+ **core**: Deduplicate `DispatchOptions` as type alias of `ExecuteTrailOptions`. Replace `TrailContext` index signature with typed `extensions` field for type safety. Deep-merge `extensions` in `executeTrail` context resolution. Remove unused `Surface` type, `adapters.ts`, `health.ts`, and `job.ts` proof-of-concept from published package.
49
+
50
+ **cli**: Remove vestigial `kind` checks from build. Run `validateTopo()` automatically in `blaze()` with opt-out via `validate: false`.
51
+
52
+ **http**: Remove vestigial `kind` checks from build. Run `validateTopo()` automatically in `blaze()` with opt-out.
53
+
54
+ **mcp**: Remove vestigial `kind` checks from build. Run `validateTopo()` automatically in `blaze()` with opt-out.
55
+
56
+ **warden**: Project-aware rule context preserved in trail wrappers.
57
+
58
+ - Updated dependencies
59
+ - @ontrails/core@1.0.0-beta.10
60
+
61
+ ## 1.0.0-beta.9
62
+
63
+ ### Minor Changes
64
+
65
+ - Consolidated improvements across all surface packages.
66
+
67
+ **core**: Add `TrailResult<T>` utility type, `topo.ids()` and `topo.count` accessors, `dispatch()` for headless trail execution, and extract shared `executeTrail` pipeline used by CLI/MCP/HTTP.
68
+
69
+ **http**: Detect route path collisions and return `Result` from `buildHttpRoutes()`, wire request `AbortSignal` through to trail context, and make write → POST mapping explicit in intent-to-method lookup.
70
+
71
+ **mcp**: Return `Result` from `buildMcpTools()` on collision instead of throwing.
72
+
73
+ **cli**: Verify exception catching via centralized `executeTrail`.
74
+
75
+ **testing**: Follow context awareness improvements.
76
+
77
+ **warden**: Refactor rules as composable trails with examples.
78
+
79
+ **schema**: Error code and empty body fixes.
80
+
81
+ ### Patch Changes
82
+
83
+ - Updated dependencies
84
+ - @ontrails/core@1.0.0-beta.9
85
+
86
+ ## 1.0.0-beta.8
87
+
88
+ ### Major Changes
89
+
90
+ - Restructure HTTP package and fix Codex review findings.
91
+
92
+ **http**: BREAKING — `blaze()` moved to `@ontrails/http/hono` subpath. Hono is now a peer dependency. `buildHttpRoutes()` is framework-agnostic. Fixed: malformed JSON → 400, execute() never throws, query parsing preserves raw strings and supports arrays.
93
+
94
+ **schema**: OpenAPI 200 response wraps in `{ data }` envelope matching wire format. Always includes 400 ValidationError with error schema. basePath trailing slash normalized.
95
+
96
+ ### Patch Changes
97
+
98
+ - @ontrails/core@1.0.0-beta.8
99
+
100
+ ## 1.0.0-beta.7
101
+
102
+ ### Minor Changes
103
+
104
+ - HTTP surface and OpenAPI generation.
105
+
106
+ **http**: New `@ontrails/http` package — Hono-based HTTP adapter. `blaze()` derives routes from trail IDs, maps intent to HTTP verbs (read→GET, write→POST, destroy→DELETE), and maps error taxonomy to status codes. Returns the Hono instance.
107
+
108
+ **schema**: Add `generateOpenApiSpec(topo)` — generates a complete OpenAPI 3.1 spec from the topo. Each trail becomes an operation with path, method, schemas, and error responses derived from the contract.
109
+
110
+ **trails**: `trails survey --openapi` outputs the OpenAPI spec for any Trails app.
111
+
112
+ ### Patch Changes
113
+
114
+ - @ontrails/core@1.0.0-beta.7
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # @ontrails/http
2
+
3
+ HTTP surface adapter. One `blaze()` call turns a topo into a Hono-based HTTP server with routes, input validation, and error mapping -- all derived from the trail contracts.
4
+
5
+ ## Usage
6
+
7
+ ```typescript
8
+ import { trail, topo, Result } from '@ontrails/core';
9
+ import { blaze } from '@ontrails/http/hono';
10
+ import { z } from 'zod';
11
+
12
+ const greet = trail('greet', {
13
+ input: z.object({ name: z.string().describe('Who to greet') }),
14
+ output: z.object({ message: z.string() }),
15
+ intent: 'read',
16
+ run: (input) => Result.ok({ message: `Hello, ${input.name}!` }),
17
+ });
18
+
19
+ const app = topo('myapp', { greet });
20
+ await blaze(app, { port: 3000 });
21
+ ```
22
+
23
+ This starts a Hono-based HTTP server. The `greet` trail becomes `GET /greet?name=...` because its `intent` is `'read'`.
24
+
25
+ For more control, build the routes yourself:
26
+
27
+ ```typescript
28
+ import { buildHttpRoutes } from '@ontrails/http';
29
+
30
+ const result = buildHttpRoutes(app);
31
+ if (result.isErr()) throw result.error; // ValidationError on route collision
32
+ for (const route of result.value) {
33
+ console.log(`${route.method} ${route.path} → ${route.trailId}`);
34
+ }
35
+ ```
36
+
37
+ `buildHttpRoutes` returns `Result<HttpRouteDefinition[], Error>` rather than a bare array. It returns `Result.err(ValidationError)` if two trails derive the same `(method, path)` pair.
38
+
39
+ ## API
40
+
41
+ | Export | What it does |
42
+ | --- | --- |
43
+ | `buildHttpRoutes(app, options?)` | Build framework-agnostic route definitions from a topo |
44
+ | `blaze(app, options?)` (`@ontrails/http/hono`) | Start a Hono HTTP server with all trails as routes |
45
+
46
+ ## Route derivation
47
+
48
+ Trail intent maps directly to HTTP method and input source:
49
+
50
+ | Trail field | HTTP method | Input source |
51
+ | --- | --- | --- |
52
+ | `intent: 'read'` | `GET` | Query string |
53
+ | `intent: 'write'` | `POST` | JSON body |
54
+ | `intent: 'destroy'` | `DELETE` | JSON body |
55
+ | (none) | `POST` | JSON body |
56
+
57
+ Trail IDs map to paths: `entity.show` becomes `/entity/show`. Dots become slashes, everything lowercase.
58
+
59
+ ## Collision detection
60
+
61
+ `buildHttpRoutes` detects when two trails would produce the same `(method, path)` pair and returns `Result.err(ValidationError)` describing both trail IDs. The `blaze()` Hono adapter throws on collision.
62
+
63
+ ## Service resolution
64
+
65
+ Declared services on each trail are resolved into the context before the implementation runs.
66
+
67
+ ## AbortSignal propagation
68
+
69
+ The `execute` function on each `HttpRouteDefinition` accepts an optional `AbortSignal`. The Hono adapter extracts `signal` from `c.req.raw` and passes it to `execute`, so client disconnects propagate into trail execution.
70
+
71
+ ## `HttpRouteDefinition`
72
+
73
+ Each route definition produced by `buildHttpRoutes` includes:
74
+
75
+ | Field | Type | What it is |
76
+ | --- | --- | --- |
77
+ | `method` | `'GET' \| 'POST' \| 'DELETE'` | HTTP method |
78
+ | `path` | `string` | Derived path (e.g. `/entity/show`) |
79
+ | `trailId` | `string` | The trail ID this route was derived from |
80
+ | `inputSource` | `'query' \| 'body'` | Where to read input |
81
+ | `trail` | `Trail` | The original trail definition |
82
+ | `execute` | `(input, requestId?, signal?) => Promise<Result>` | Validates, layers, and runs the implementation |
83
+
84
+ ## Installation
85
+
86
+ ```bash
87
+ bun add @ontrails/http hono
88
+ ```
@@ -0,0 +1,25 @@
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
@@ -0,0 +1 @@
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 ADDED
@@ -0,0 +1,69 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,52 @@
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 layers, and runs the
6
+ * implementation -- all without referencing any HTTP framework types.
7
+ */
8
+ import { Result } from '@ontrails/core';
9
+ import type { Layer, ServiceOverrideMap, Topo, Trail, TrailContextInit } from '@ontrails/core';
10
+ export interface BuildHttpRoutesOptions {
11
+ readonly basePath?: string | undefined;
12
+ /** Config values for services that declare a `config` schema, keyed by service ID. */
13
+ readonly configValues?: Readonly<Record<string, Record<string, unknown>>> | undefined;
14
+ readonly createContext?: (() => TrailContextInit | Promise<TrailContextInit>) | undefined;
15
+ readonly layers?: readonly Layer[] | undefined;
16
+ readonly services?: ServiceOverrideMap | undefined;
17
+ }
18
+ export type HttpMethod = 'GET' | 'POST' | 'DELETE';
19
+ /** Input source derived from the HTTP method. */
20
+ export type InputSource = 'query' | 'body';
21
+ export interface HttpRouteDefinition {
22
+ readonly method: HttpMethod;
23
+ readonly path: string;
24
+ readonly trailId: string;
25
+ readonly inputSource: InputSource;
26
+ readonly trail: Trail<unknown, unknown>;
27
+ /**
28
+ * Validate input, compose layers, and execute the trail implementation.
29
+ *
30
+ * The caller is responsible for parsing raw input from the request and
31
+ * mapping the Result to an HTTP response. This function is framework-agnostic.
32
+ *
33
+ * @param signal - Optional AbortSignal from the HTTP request. When provided,
34
+ * it takes final precedence over any context factory signal, allowing
35
+ * client-initiated cancellation to propagate into trail execution.
36
+ */
37
+ readonly execute: (input: unknown, requestId?: string | undefined, signal?: AbortSignal | undefined) => Promise<Result<unknown, Error>>;
38
+ }
39
+ /**
40
+ * Build HTTP route definitions from a topo.
41
+ *
42
+ * Each trail becomes an HttpRouteDefinition with:
43
+ * - An HTTP method derived from intent (read -> GET, write -> POST, destroy -> DELETE)
44
+ * - A path derived from the trail ID (dots become slashes)
45
+ * - An input source derived from the method (GET -> query, others -> body)
46
+ * - An `execute` function that validates, layers, and runs the implementation
47
+ *
48
+ * Returns `Result.err(ValidationError)` if two trails derive the same
49
+ * (method, path) pair. Returns `Result.ok(routes)` on success.
50
+ */
51
+ export declare const buildHttpRoutes: (app: Topo, options?: BuildHttpRoutesOptions) => Result<HttpRouteDefinition[], Error>;
52
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,MAAM,EAIP,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,KAAK,EACL,kBAAkB,EAClB,IAAI,EACJ,KAAK,EACL,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAMxB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,sFAAsF;IACtF,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,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;CACpD;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,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,KAC7B,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,CAIrC,CAAC"}
package/dist/build.js ADDED
@@ -0,0 +1,121 @@
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 layers, and runs the
6
+ * implementation -- all without referencing any HTTP framework types.
7
+ */
8
+ import { Result, SURFACE_KEY, ValidationError, executeTrail, } from '@ontrails/core';
9
+ // ---------------------------------------------------------------------------
10
+ // Internal helpers
11
+ // ---------------------------------------------------------------------------
12
+ /** Explicit intent → HTTP method mapping. */
13
+ const intentToMethod = {
14
+ destroy: 'DELETE',
15
+ read: 'GET',
16
+ write: 'POST',
17
+ };
18
+ /** Derive HTTP method from trail intent. */
19
+ const deriveMethod = (trail) => intentToMethod[trail.intent] ?? 'POST';
20
+ /** Derive HTTP path from trail ID: `entity.show` -> `/entity/show`. */
21
+ const derivePath = (basePath, trailId) => {
22
+ const segments = trailId.replaceAll('.', '/');
23
+ const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;
24
+ return `${base}/${segments}`;
25
+ };
26
+ /** Derive input source from HTTP method. */
27
+ const deriveInputSource = (method) => method === 'GET' ? 'query' : 'body';
28
+ /** Check if a trail should be included (skip internal trails). */
29
+ const shouldInclude = (trail) => trail.metadata?.['internal'] !== true;
30
+ /** Build per-request context overrides with the HTTP surface marker. */
31
+ const withHttpSurface = (requestId) => ({
32
+ ...(requestId === undefined ? {} : { requestId }),
33
+ extensions: {
34
+ [SURFACE_KEY]: 'http',
35
+ },
36
+ });
37
+ // ---------------------------------------------------------------------------
38
+ // Execute factory
39
+ // ---------------------------------------------------------------------------
40
+ /**
41
+ * Create an `execute` function for a single trail.
42
+ *
43
+ * Delegates to the centralized `executeTrail` pipeline in core.
44
+ * The returned function returns a `Result` and never throws.
45
+ */
46
+ const createExecute = (t, layers, options) => (input, requestId, signal) => executeTrail(t, input, {
47
+ configValues: options.configValues,
48
+ createContext: options.createContext,
49
+ ctx: withHttpSurface(requestId),
50
+ layers,
51
+ services: options.services,
52
+ signal,
53
+ });
54
+ // ---------------------------------------------------------------------------
55
+ // Builder helpers
56
+ // ---------------------------------------------------------------------------
57
+ /** Filter topo items to eligible trails. */
58
+ const eligibleTrails = (app) => app.list().filter((trail) => shouldInclude(trail));
59
+ /** Build a single route definition from a trail. */
60
+ const buildRoute = (trail, basePath, layers, options) => {
61
+ const method = deriveMethod(trail);
62
+ const path = derivePath(basePath, trail.id);
63
+ return {
64
+ execute: createExecute(trail, layers, options),
65
+ inputSource: deriveInputSource(method),
66
+ method,
67
+ path,
68
+ trail,
69
+ trailId: trail.id,
70
+ };
71
+ };
72
+ // ---------------------------------------------------------------------------
73
+ // Collision detection
74
+ // ---------------------------------------------------------------------------
75
+ /** Derive the lookup key for (method, path) collision detection. */
76
+ const routeKey = (route) => `${route.method} ${route.path}`;
77
+ /** Register a route, checking for (path, method) collisions. */
78
+ const registerRoute = (route, seenRoutes, routes) => {
79
+ const key = routeKey(route);
80
+ const existingId = seenRoutes.get(key);
81
+ if (existingId !== undefined) {
82
+ return Result.err(new ValidationError(`HTTP route collision: trails "${existingId}" and "${route.trailId}" both derive ${route.method} ${route.path}`));
83
+ }
84
+ seenRoutes.set(key, route.trailId);
85
+ routes.push(route);
86
+ return Result.ok();
87
+ };
88
+ /** Accumulate route definitions, returning early on the first collision. */
89
+ const accumulateRoutes = (trails, basePath, layers, options) => {
90
+ const routes = [];
91
+ const seenRoutes = new Map();
92
+ for (const trail of trails) {
93
+ const route = buildRoute(trail, basePath, layers, options);
94
+ const registered = registerRoute(route, seenRoutes, routes);
95
+ if (registered.isErr()) {
96
+ return registered;
97
+ }
98
+ }
99
+ return Result.ok(routes);
100
+ };
101
+ // ---------------------------------------------------------------------------
102
+ // Builder
103
+ // ---------------------------------------------------------------------------
104
+ /**
105
+ * Build HTTP route definitions from a topo.
106
+ *
107
+ * Each trail becomes an HttpRouteDefinition with:
108
+ * - An HTTP method derived from intent (read -> GET, write -> POST, destroy -> DELETE)
109
+ * - A path derived from the trail ID (dots become slashes)
110
+ * - An input source derived from the method (GET -> query, others -> body)
111
+ * - An `execute` function that validates, layers, and runs the implementation
112
+ *
113
+ * Returns `Result.err(ValidationError)` if two trails derive the same
114
+ * (method, path) pair. Returns `Result.ok(routes)` on success.
115
+ */
116
+ export const buildHttpRoutes = (app, options = {}) => {
117
+ const basePath = (options.basePath ?? '').replace(/\/+$/, '');
118
+ const layers = options.layers ?? [];
119
+ return accumulateRoutes(eligibleTrails(app), basePath, layers, options);
120
+ };
121
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Hono adapter for Trails HTTP routes.
3
+ *
4
+ * Takes framework-agnostic HttpRouteDefinition[] and wires them into a
5
+ * Hono application, handling request parsing, response mapping, and errors.
6
+ *
7
+ * ```ts
8
+ * const app = topo("myapp", entity);
9
+ * await blaze(app, { port: 3000 });
10
+ * ```
11
+ */
12
+ import type { Layer, ServiceOverrideMap, Topo, TrailContextInit } from '@ontrails/core';
13
+ import { Hono } from 'hono';
14
+ export interface BlazeHttpOptions {
15
+ readonly basePath?: string | undefined;
16
+ /** Config values for services that declare a `config` schema, keyed by service ID. */
17
+ readonly configValues?: Readonly<Record<string, Record<string, unknown>>> | undefined;
18
+ readonly createContext?: (() => TrailContextInit | Promise<TrailContextInit>) | undefined;
19
+ readonly hostname?: string | undefined;
20
+ readonly layers?: readonly Layer[] | undefined;
21
+ readonly name?: string | undefined;
22
+ readonly port?: number | undefined;
23
+ readonly services?: ServiceOverrideMap | undefined;
24
+ /** Set false to return the Hono app without starting a server. */
25
+ readonly serve?: boolean | undefined;
26
+ /** Set to `false` to skip topo validation at startup. Defaults to `true`. */
27
+ readonly validate?: boolean | undefined;
28
+ }
29
+ /**
30
+ * Build HTTP routes from a topo, create a Hono app, and optionally start serving.
31
+ */
32
+ export declare const blaze: (app: Topo, options?: BlazeHttpOptions) => Promise<Hono>;
33
+ //# sourceMappingURL=blaze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blaze.d.ts","sourceRoot":"","sources":["../../src/hono/blaze.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EACV,KAAK,EACL,kBAAkB,EAClB,IAAI,EACJ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAY5B,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,sFAAsF;IACtF,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,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,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACnD,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAyQD;;GAEG;AAEH,eAAO,MAAM,KAAK,GAChB,KAAK,IAAI,EACT,UAAS,gBAAqB,KAC7B,OAAO,CAAC,IAAI,CA8Bd,CAAC"}