@ontrails/core 1.0.0-beta.7 → 1.0.0-beta.9

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.
@@ -1,3 +1,3 @@
1
1
  $ oxlint ./src
2
2
  Found 0 warnings and 0 errors.
3
- Finished in 23ms on 60 files with 93 rules using 24 threads.
3
+ Finished in 14ms on 64 files with 93 rules using 24 threads.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @ontrails/core
2
2
 
3
+ ## 1.0.0-beta.9
4
+
5
+ ### Minor Changes
6
+
7
+ - Consolidated improvements across all surface packages.
8
+
9
+ **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.
10
+
11
+ **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.
12
+
13
+ **mcp**: Return `Result` from `buildMcpTools()` on collision instead of throwing.
14
+
15
+ **cli**: Verify exception catching via centralized `executeTrail`.
16
+
17
+ **testing**: Follow context awareness improvements.
18
+
19
+ **warden**: Refactor rules as composable trails with examples.
20
+
21
+ **schema**: Error code and empty body fixes.
22
+
23
+ ## 1.0.0-beta.8
24
+
3
25
  ## 1.0.0-beta.7
4
26
 
5
27
  ## 1.0.0-beta.6
package/README.md CHANGED
@@ -46,15 +46,51 @@ const onboard = trail('entity.onboard', {
46
46
  | `topo(name, ...modules)` | Collect trail modules into a queryable topology |
47
47
  | `validateTopo(topo)` | Structural validation: follow targets exist, no cycles, examples parse, output schemas present |
48
48
 
49
+ ### Execution
50
+
51
+ | Export | What it does |
52
+ | --- | --- |
53
+ | `executeTrail(trail, rawInput, options?)` | Centralized execution pipeline: validates input, builds context, composes layers, runs the implementation. Never throws -- exceptions become `Result.err(InternalError)`. |
54
+ | `dispatch(topo, id, input, options?)` | Headless trail execution by ID. Looks up the trail in the topo, then delegates to `executeTrail`. Returns `Result.err(NotFoundError)` if the ID is not registered. |
55
+
56
+ ```typescript
57
+ // executeTrail — surface adapters use this directly
58
+ const result = await executeTrail(greet, { name: 'Alice' });
59
+
60
+ // dispatch — no-surface execution by trail ID
61
+ const result = await dispatch(app, 'greet', { name: 'Alice' });
62
+ if (result.isOk()) console.log(result.value);
63
+ ```
64
+
65
+ ### Topo accessors
66
+
67
+ Beyond the `trail(id, spec)` builder, `Topo` exposes these accessors:
68
+
69
+ | Accessor | What it returns |
70
+ | --- | --- |
71
+ | `topo.ids()` | `string[]` of all registered trail IDs |
72
+ | `topo.count` | Number of registered trails |
73
+ | `topo.get(id)` | The `Trail` with that ID, or `undefined` |
74
+ | `topo.has(id)` | Whether a trail ID is registered |
75
+ | `topo.list()` | All registered trails as an array |
76
+
49
77
  ### Type utilities
50
78
 
51
79
  | Export | What it does |
52
80
  | --- | --- |
53
81
  | `TrailInput<T>` | Extract the input type from a `Trail` |
54
82
  | `TrailOutput<T>` | Extract the output type from a `Trail` |
83
+ | `TrailResult<T>` | `Result<TrailOutput<T>, Error>` -- the Result type for a trail's output |
55
84
  | `inputOf(trail)` | Get the input Zod schema from a trail instance |
56
85
  | `outputOf(trail)` | Get the output Zod schema (or `undefined`) from a trail instance |
57
86
 
87
+ ### Execution option types
88
+
89
+ | Type | What it describes |
90
+ | --- | --- |
91
+ | `ExecuteTrailOptions` | Options for `executeTrail`: `ctx`, `signal`, `layers`, `createContext` |
92
+ | `DispatchOptions` | Same shape as `ExecuteTrailOptions`; forwarded by `dispatch` |
93
+
58
94
  ### Result
59
95
 
60
96
  ```typescript
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Headless trail execution — the "no-surface" surface.
3
+ *
4
+ * Looks up a trail by ID in a topo, then delegates to `executeTrail`.
5
+ * Returns a `Result` and never throws.
6
+ */
7
+ import type { Topo } from './topo.js';
8
+ import type { TrailContext } from './types.js';
9
+ import type { Layer } from './layer.js';
10
+ import { Result } from './result.js';
11
+ /** Options forwarded to `executeTrail` from `dispatch`. */
12
+ export interface DispatchOptions {
13
+ /** Partial context overrides merged on top of the base context. */
14
+ readonly ctx?: Partial<TrailContext> | undefined;
15
+ /** AbortSignal override (takes final precedence over ctx and factory). */
16
+ readonly signal?: AbortSignal | undefined;
17
+ /** Layers to compose around the implementation. */
18
+ readonly layers?: readonly Layer[] | undefined;
19
+ /** Factory that produces a base TrailContext (takes precedence over defaults). */
20
+ readonly createContext?: (() => TrailContext | Promise<TrailContext>) | undefined;
21
+ }
22
+ /**
23
+ * Execute a trail by ID from a topo without mounting a surface.
24
+ *
25
+ * Resolves the trail from the topo, then runs it through the standard
26
+ * `executeTrail` pipeline. Returns `Result.err(NotFoundError)` if the
27
+ * trail ID is not registered. Never throws — unexpected exceptions are
28
+ * returned as `Result.err(InternalError)`.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const result = await dispatch(myTopo, 'greet', { name: 'Alice' });
33
+ * if (result.isOk()) console.log(result.value);
34
+ * ```
35
+ */
36
+ export declare const dispatch: (topo: Topo, id: string, input: unknown, options?: DispatchOptions) => Promise<Result<unknown, Error>>;
37
+ //# sourceMappingURL=dispatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGxC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAMrC,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjD,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,kFAAkF;IAClF,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,GAC5C,SAAS,CAAC;CACf;AAMD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,QAAQ,GACnB,MAAM,IAAI,EACV,IAAI,MAAM,EACV,OAAO,OAAO,EACd,UAAU,eAAe,KACxB,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAUhC,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Headless trail execution — the "no-surface" surface.
3
+ *
4
+ * Looks up a trail by ID in a topo, then delegates to `executeTrail`.
5
+ * Returns a `Result` and never throws.
6
+ */
7
+ import { executeTrail } from './execute.js';
8
+ import { NotFoundError } from './errors.js';
9
+ import { Result } from './result.js';
10
+ // ---------------------------------------------------------------------------
11
+ // dispatch()
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * Execute a trail by ID from a topo without mounting a surface.
15
+ *
16
+ * Resolves the trail from the topo, then runs it through the standard
17
+ * `executeTrail` pipeline. Returns `Result.err(NotFoundError)` if the
18
+ * trail ID is not registered. Never throws — unexpected exceptions are
19
+ * returned as `Result.err(InternalError)`.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const result = await dispatch(myTopo, 'greet', { name: 'Alice' });
24
+ * if (result.isOk()) console.log(result.value);
25
+ * ```
26
+ */
27
+ export const dispatch = (topo, id, input, options) => {
28
+ const trail = topo.get(id);
29
+ if (trail === undefined) {
30
+ return Promise.resolve(Result.err(new NotFoundError(`Trail "${id}" not found in topo "${topo.name}"`)));
31
+ }
32
+ return executeTrail(trail, input, options);
33
+ };
34
+ //# sourceMappingURL=dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAoBrC,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,IAAU,EACV,EAAU,EACV,KAAc,EACd,OAAyB,EACQ,EAAE;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC,OAAO,CACpB,MAAM,CAAC,GAAG,CACR,IAAI,aAAa,CAAC,UAAU,EAAE,wBAAwB,IAAI,CAAC,IAAI,GAAG,CAAC,CACpE,CACF,CAAC;IACJ,CAAC;IACD,OAAO,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Centralized trail execution pipeline.
3
+ *
4
+ * Validates input, builds context, composes layers, and runs the
5
+ * implementation. Surfaces (CLI, MCP, HTTP) delegate here instead
6
+ * of reimplementing the pipeline.
7
+ */
8
+ import type { AnyTrail } from './trail.js';
9
+ import type { Layer } from './layer.js';
10
+ import type { TrailContext } from './types.js';
11
+ import { Result } from './result.js';
12
+ /** Options for executeTrail. */
13
+ export interface ExecuteTrailOptions {
14
+ /** Partial context overrides merged on top of the base context. */
15
+ readonly ctx?: Partial<TrailContext> | undefined;
16
+ /** AbortSignal override (takes final precedence over ctx and factory). */
17
+ readonly signal?: AbortSignal | undefined;
18
+ /** Layers to compose around the implementation. */
19
+ readonly layers?: readonly Layer[] | undefined;
20
+ /** Factory that produces a base TrailContext (takes precedence over defaults). */
21
+ readonly createContext?: (() => TrailContext | Promise<TrailContext>) | undefined;
22
+ }
23
+ /**
24
+ * Execute a trail through the standard validate-context-layers-run pipeline.
25
+ *
26
+ * The function never throws -- unexpected exceptions are caught and
27
+ * returned as `Result.err(InternalError)`.
28
+ */
29
+ export declare const executeTrail: (trail: AnyTrail, rawInput: unknown, options?: ExecuteTrailOptions) => Promise<Result<unknown, Error>>;
30
+ //# sourceMappingURL=execute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAOrC,gCAAgC;AAChC,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjD,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,KAAK,EAAE,GAAG,SAAS,CAAC;IAC/C,kFAAkF;IAClF,QAAQ,CAAC,aAAa,CAAC,EACnB,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,GAC5C,SAAS,CAAC;CACf;AA8BD;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GACvB,OAAO,QAAQ,EACf,UAAU,OAAO,EACjB,UAAU,mBAAmB,KAC5B,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAehC,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Centralized trail execution pipeline.
3
+ *
4
+ * Validates input, builds context, composes layers, and runs the
5
+ * implementation. Surfaces (CLI, MCP, HTTP) delegate here instead
6
+ * of reimplementing the pipeline.
7
+ */
8
+ import { composeLayers } from './layer.js';
9
+ import { createTrailContext } from './context.js';
10
+ import { InternalError } from './errors.js';
11
+ import { Result } from './result.js';
12
+ import { validateInput } from './validation.js';
13
+ // ---------------------------------------------------------------------------
14
+ // Context resolution
15
+ // ---------------------------------------------------------------------------
16
+ /**
17
+ * Build a TrailContext from options.
18
+ *
19
+ * Resolution order:
20
+ * 1. Factory (`createContext`) or `createTrailContext()` defaults.
21
+ * 2. Partial `ctx` overrides merged on top.
22
+ * 3. `signal` override takes final precedence.
23
+ */
24
+ const resolveContext = async (options) => {
25
+ const base = options?.createContext
26
+ ? await options.createContext()
27
+ : createTrailContext();
28
+ const withOverrides = options?.ctx ? { ...base, ...options.ctx } : base;
29
+ return options?.signal
30
+ ? { ...withOverrides, signal: options.signal }
31
+ : withOverrides;
32
+ };
33
+ // ---------------------------------------------------------------------------
34
+ // Pipeline
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Execute a trail through the standard validate-context-layers-run pipeline.
38
+ *
39
+ * The function never throws -- unexpected exceptions are caught and
40
+ * returned as `Result.err(InternalError)`.
41
+ */
42
+ export const executeTrail = async (trail, rawInput, options) => {
43
+ try {
44
+ const validated = validateInput(trail.input, rawInput);
45
+ if (validated.isErr()) {
46
+ return validated;
47
+ }
48
+ const ctx = await resolveContext(options);
49
+ const layers = options?.layers ?? [];
50
+ const impl = composeLayers([...layers], trail, trail.run);
51
+ return await impl(validated.value, ctx);
52
+ }
53
+ catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ return Result.err(new InternalError(message));
56
+ }
57
+ };
58
+ //# sourceMappingURL=execute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execute.js","sourceRoot":"","sources":["../src/execute.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAoBhD,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,KAAK,EAC1B,OAA6B,EACN,EAAE;IACzB,MAAM,IAAI,GAAG,OAAO,EAAE,aAAa;QACjC,CAAC,CAAC,MAAM,OAAO,CAAC,aAAa,EAAE;QAC/B,CAAC,CAAC,kBAAkB,EAAE,CAAC;IACzB,MAAM,aAAa,GAAG,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,OAAO,OAAO,EAAE,MAAM;QACpB,CAAC,CAAC,EAAE,GAAG,aAAa,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;QAC9C,CAAC,CAAC,aAAa,CAAC;AACpB,CAAC,CAAC;AAEF,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,KAAe,EACf,QAAiB,EACjB,OAA6B,EACI,EAAE;IACnC,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACvD,IAAI,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;YACtB,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,aAAa,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1D,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IAChD,CAAC;AACH,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export type { Implementation, TrailContext, FollowFn, ProgressCallback, Progress
5
5
  export { createTrailContext } from './context.js';
6
6
  export { trail } from './trail.js';
7
7
  export type { AnyTrail, Intent, Trail, TrailSpec, TrailExample, } from './trail.js';
8
- export type { TrailInput, TrailOutput } from './type-utils.js';
8
+ export type { TrailInput, TrailOutput, TrailResult } from './type-utils.js';
9
9
  export { inputOf, outputOf } from './type-utils.js';
10
10
  export { event } from './event.js';
11
11
  export type { AnyEvent, Event, EventSpec } from './event.js';
@@ -19,6 +19,10 @@ export type { HealthStatus, HealthResult } from './health.js';
19
19
  export type { IndexAdapter, StorageAdapter, CacheAdapter, SearchOptions, SearchResult, StorageOptions, } from './adapters.js';
20
20
  export { deriveFields } from './derive.js';
21
21
  export type { Field, FieldOverride } from './derive.js';
22
+ export { executeTrail } from './execute.js';
23
+ export type { ExecuteTrailOptions } from './execute.js';
24
+ export { dispatch } from './dispatch.js';
25
+ export type { DispatchOptions } from './dispatch.js';
22
26
  export { validateInput, validateOutput, formatZodIssues, zodToJsonSchema, } from './validation.js';
23
27
  export { serializeError, deserializeError } from './serialization.js';
24
28
  export type { SerializedError } from './serialization.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -17,6 +17,10 @@ export { validateTopo } from './validate-topo.js';
17
17
  export { composeLayers } from './layer.js';
18
18
  // Derive
19
19
  export { deriveFields } from './derive.js';
20
+ // Execute
21
+ export { executeTrail } from './execute.js';
22
+ // Dispatch
23
+ export { dispatch } from './dispatch.js';
20
24
  // Validation
21
25
  export { validateInput, validateOutput, formatZodIssues, zodToJsonSchema, } from './validation.js';
22
26
  // Serialization
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,SAAS;AACT,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,aAAa,EACb,SAAS,EACT,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAAW,EACX,aAAa,GACd,MAAM,aAAa,CAAC;AAcrB,kBAAkB;AAClB,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,QAAQ;AACR,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAWnC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEpD,QAAQ;AACR,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO;AACP,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,QAAQ;AACR,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAgB3C,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,aAAa;AACb,OAAO,EACL,aAAa,EACb,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,gBAAgB;AAChB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtE,aAAa;AACb,OAAO,EACL,KAAK,EACL,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAGzB,uDAAuD;AAEvD,gBAAgB;AAChB,OAAO,EACL,KAAK,EACL,OAAO,EACP,IAAI,EACJ,KAAK,EACL,cAAc,EACd,WAAW,EACX,OAAO,EACP,MAAM,GACP,MAAM,cAAc,CAAC;AAStB,gBAAgB;AAChB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE7E,YAAY;AACZ,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO;AACP,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAGzD,SAAS;AACT,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,cAAc;AACd,OAAO,EACL,KAAK,EACL,MAAM,EACN,OAAO,EACP,MAAM,EACN,eAAe,GAChB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,SAAS;AACT,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,SAAS;AACT,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,aAAa,EACb,SAAS,EACT,cAAc,EACd,WAAW,EACX,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAAW,EACX,aAAa,GACd,MAAM,aAAa,CAAC;AAcrB,kBAAkB;AAClB,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,QAAQ;AACR,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAWnC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEpD,QAAQ;AACR,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO;AACP,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,QAAQ;AACR,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAgB3C,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,UAAU;AACV,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,WAAW;AACX,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,aAAa;AACb,OAAO,EACL,aAAa,EACb,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,gBAAgB;AAChB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtE,aAAa;AACb,OAAO,EACL,KAAK,EACL,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAGzB,uDAAuD;AAEvD,gBAAgB;AAChB,OAAO,EACL,KAAK,EACL,OAAO,EACP,IAAI,EACJ,KAAK,EACL,cAAc,EACd,WAAW,EACX,OAAO,EACP,MAAM,GACP,MAAM,cAAc,CAAC;AAStB,gBAAgB;AAChB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE7E,YAAY;AACZ,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,OAAO;AACP,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAGzD,SAAS;AACT,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,cAAc;AACd,OAAO,EACL,KAAK,EACL,MAAM,EACN,OAAO,EACP,MAAM,EACN,eAAe,GAChB,MAAM,kBAAkB,CAAC"}
package/dist/topo.d.ts CHANGED
@@ -7,8 +7,10 @@ export interface Topo {
7
7
  readonly name: string;
8
8
  readonly trails: ReadonlyMap<string, AnyTrail>;
9
9
  readonly events: ReadonlyMap<string, AnyEvent>;
10
+ readonly count: number;
10
11
  get(id: string): AnyTrail | undefined;
11
12
  has(id: string): boolean;
13
+ ids(): string[];
12
14
  list(): AnyTrail[];
13
15
  listEvents(): AnyEvent[];
14
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"topo.d.ts","sourceRoot":"","sources":["../src/topo.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM3C,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IACtC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,IAAI,IAAI,QAAQ,EAAE,CAAC;IACnB,UAAU,IAAI,QAAQ,EAAE,CAAC;CAC1B;AA0ED,eAAO,MAAM,IAAI,GACf,MAAM,MAAM,EACZ,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KACpC,IAaF,CAAC"}
1
+ {"version":3,"file":"topo.d.ts","sourceRoot":"","sources":["../src/topo.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM3C,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IACtC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,GAAG,IAAI,MAAM,EAAE,CAAC;IAChB,IAAI,IAAI,QAAQ,EAAE,CAAC;IACnB,UAAU,IAAI,QAAQ,EAAE,CAAC;CAC1B;AA+ED,eAAO,MAAM,IAAI,GACf,MAAM,MAAM,EACZ,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KACpC,IAaF,CAAC"}
package/dist/topo.js CHANGED
@@ -13,6 +13,7 @@ const isRegistrable = (value) => {
13
13
  // Topo implementation
14
14
  // ---------------------------------------------------------------------------
15
15
  const createTopo = (name, trails, events) => ({
16
+ count: trails.size,
16
17
  events,
17
18
  get(id) {
18
19
  return trails.get(id);
@@ -20,6 +21,9 @@ const createTopo = (name, trails, events) => ({
20
21
  has(id) {
21
22
  return trails.has(id);
22
23
  },
24
+ ids() {
25
+ return [...trails.keys()];
26
+ },
23
27
  list() {
24
28
  return [...trails.values()];
25
29
  },
package/dist/topo.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"topo.js","sourceRoot":"","sources":["../src/topo.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAwB9C,MAAM,aAAa,GAAG,CAAC,KAAc,EAAwB,EAAE;IAC7D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,GAAG,KAAgC,CAAC;IAClD,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,CAAC;AAC9C,CAAC,CAAC;AAEF,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,MAAM,UAAU,GAAG,CACjB,IAAY,EACZ,MAAqC,EACrC,MAAqC,EAC/B,EAAE,CAAC,CAAC;IACV,MAAM;IACN,GAAG,CAAC,EAAU;QACZ,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IACD,GAAG,CAAC,EAAU;QACZ,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,UAAU;QACR,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,IAAI;IAEJ,MAAM;CACP,CAAC,CAAC;AAEH,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E,oEAAoE;AACpE,MAAM,QAAQ,GAAG,CACf,KAAkB,EAClB,MAA6B,EAC7B,MAA6B,EACvB,EAAE;IACR,MAAM,EAAE,EAAE,EAAE,GAAG,KAAuB,CAAC;IACvC,MAAM,UAAU,GAA+B;QAC7C,KAAK,EAAE,GAAG,EAAE;YACV,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBACnB,MAAM,IAAI,eAAe,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;YAC3D,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAiB,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,EAAE,GAAG,EAAE;YACV,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBACnB,MAAM,IAAI,eAAe,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;YAC3D,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAiB,CAAC,CAAC;QACpC,CAAC;KACF,CAAC;IACF,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,IAAY,EACZ,GAAG,OAAkC,EAC/B,EAAE;IACR,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE3C,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACvC,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAC1C,CAAC,CAAC"}
1
+ {"version":3,"file":"topo.js","sourceRoot":"","sources":["../src/topo.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA0B9C,MAAM,aAAa,GAAG,CAAC,KAAc,EAAwB,EAAE;IAC7D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,EAAE,IAAI,EAAE,GAAG,KAAgC,CAAC;IAClD,OAAO,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,CAAC;AAC9C,CAAC,CAAC;AAEF,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,MAAM,UAAU,GAAG,CACjB,IAAY,EACZ,MAAqC,EACrC,MAAqC,EAC/B,EAAE,CAAC,CAAC;IACV,KAAK,EAAE,MAAM,CAAC,IAAI;IAClB,MAAM;IACN,GAAG,CAAC,EAAU;QACZ,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IACD,GAAG,CAAC,EAAU;QACZ,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;IAED,GAAG;QACD,OAAO,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,UAAU;QACR,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,IAAI;IAEJ,MAAM;CACP,CAAC,CAAC;AAEH,8EAA8E;AAC9E,SAAS;AACT,8EAA8E;AAE9E,oEAAoE;AACpE,MAAM,QAAQ,GAAG,CACf,KAAkB,EAClB,MAA6B,EAC7B,MAA6B,EACvB,EAAE;IACR,MAAM,EAAE,EAAE,EAAE,GAAG,KAAuB,CAAC;IACvC,MAAM,UAAU,GAA+B;QAC7C,KAAK,EAAE,GAAG,EAAE;YACV,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBACnB,MAAM,IAAI,eAAe,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;YAC3D,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAiB,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,EAAE,GAAG,EAAE;YACV,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBACnB,MAAM,IAAI,eAAe,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;YAC3D,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,KAAiB,CAAC,CAAC;QACpC,CAAC;KACF,CAAC;IACF,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,IAAY,EACZ,GAAG,OAAkC,EAC/B,EAAE;IACR,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE3C,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACvC,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAC1C,CAAC,CAAC"}
@@ -1,11 +1,22 @@
1
1
  /**
2
2
  * Type utilities for extracting input/output types from trails.
3
3
  */
4
+ import type { Result } from './result.js';
4
5
  import type { AnyTrail, Trail } from './trail.js';
5
6
  /** Extract the input type from a Trail. */
6
7
  export type TrailInput<T extends AnyTrail> = T extends Trail<infer I, any> ? I : never;
7
8
  /** Extract the output type from a Trail. */
8
9
  export type TrailOutput<T extends AnyTrail> = T extends Trail<any, infer O> ? O : never;
10
+ /**
11
+ * Extracts the full `Result<Output, Error>` type from a trail definition.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * type SearchResult = TrailResult<typeof searchTrail>;
16
+ * // Result<{ results: Item[]; count: number }, Error>
17
+ * ```
18
+ */
19
+ export type TrailResult<T extends AnyTrail> = Result<TrailOutput<T>, Error>;
9
20
  /** Get the input Zod schema from a trail, preserving the specific schema type. */
10
21
  export declare const inputOf: <T extends AnyTrail>(trail: T) => T["input"];
11
22
  /** Get the output Zod schema from a trail, if defined, preserving the specific schema type. */
@@ -1 +1 @@
1
- {"version":3,"file":"type-utils.d.ts","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAQlD,2CAA2C;AAC3C,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,QAAQ,IACvC,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE5C,4CAA4C;AAC5C,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,QAAQ,IACxC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAQ5C,kFAAkF;AAClF,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,QAAQ,EAAE,OAAO,CAAC,KAAG,CAAC,CAAC,OAAO,CACnD,CAAC;AAEd,+FAA+F;AAC/F,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,QAAQ,EAAE,OAAO,CAAC,KAAG,CAAC,CAAC,QAAQ,CACpD,CAAC"}
1
+ {"version":3,"file":"type-utils.d.ts","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAQlD,2CAA2C;AAC3C,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,QAAQ,IACvC,CAAC,SAAS,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE5C,4CAA4C;AAC5C,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,QAAQ,IACxC,CAAC,SAAS,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAE5C;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,QAAQ,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAQ5E,kFAAkF;AAClF,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,QAAQ,EAAE,OAAO,CAAC,KAAG,CAAC,CAAC,OAAO,CACnD,CAAC;AAEd,+FAA+F;AAC/F,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,QAAQ,EAAE,OAAO,CAAC,KAAG,CAAC,CAAC,QAAQ,CACpD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"type-utils.js","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkBH,mCAAmC;AAEnC,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E,kFAAkF;AAClF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAqB,KAAQ,EAAc,EAAE,CAClE,KAAK,CAAC,KAAK,CAAC;AAEd,+FAA+F;AAC/F,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAqB,KAAQ,EAAe,EAAE,CACpE,KAAK,CAAC,MAAM,CAAC"}
1
+ {"version":3,"file":"type-utils.js","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AA8BH,mCAAmC;AAEnC,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E,kFAAkF;AAClF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAqB,KAAQ,EAAc,EAAE,CAClE,KAAK,CAAC,KAAK,CAAC;AAEd,+FAA+F;AAC/F,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAqB,KAAQ,EAAe,EAAE,CACpE,KAAK,CAAC,MAAM,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/core",
3
- "version": "1.0.0-beta.7",
3
+ "version": "1.0.0-beta.9",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -0,0 +1,154 @@
1
+ /* oxlint-disable require-await -- trail implementations satisfy async interface without awaiting */
2
+ import { describe, test, expect } from 'bun:test';
3
+
4
+ import { z } from 'zod';
5
+
6
+ import { dispatch } from '../dispatch';
7
+ import { InternalError, NotFoundError, ValidationError } from '../errors';
8
+ import type { Layer } from '../layer';
9
+ import { Result } from '../result';
10
+ import { topo } from '../topo';
11
+ import { trail } from '../trail';
12
+ import type { TrailContext } from '../types';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Fixtures
16
+ // ---------------------------------------------------------------------------
17
+
18
+ const echoTrail = trail('echo', {
19
+ input: z.object({ value: z.string() }),
20
+ output: z.object({ value: z.string() }),
21
+ run: (input) => Result.ok({ value: input.value }),
22
+ });
23
+
24
+ const throwingTrail = trail('throws', {
25
+ input: z.object({}),
26
+ run: () => {
27
+ throw new Error('kaboom');
28
+ },
29
+ });
30
+
31
+ const testTopo = topo('test', { echoTrail, throwingTrail });
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Tests
35
+ // ---------------------------------------------------------------------------
36
+
37
+ describe('dispatch', () => {
38
+ describe('happy path', () => {
39
+ test('dispatches by ID and returns Result.ok with expected value', async () => {
40
+ const result = await dispatch(testTopo, 'echo', { value: 'hello' });
41
+
42
+ expect(result.isOk()).toBe(true);
43
+ expect(result.unwrap()).toEqual({ value: 'hello' });
44
+ });
45
+ });
46
+
47
+ describe('not found', () => {
48
+ test('returns NotFoundError for unknown trail ID', async () => {
49
+ const result = await dispatch(testTopo, 'nonexistent', {});
50
+
51
+ expect(result.isErr()).toBe(true);
52
+ expect(result.error).toBeInstanceOf(NotFoundError);
53
+ expect(result.error.message).toContain('nonexistent');
54
+ expect(result.error.message).toContain('test');
55
+ });
56
+ });
57
+
58
+ describe('validation', () => {
59
+ test('returns ValidationError for invalid input', async () => {
60
+ const result = await dispatch(testTopo, 'echo', { value: 42 });
61
+
62
+ expect(result.isErr()).toBe(true);
63
+ expect(result.error).toBeInstanceOf(ValidationError);
64
+ });
65
+ });
66
+
67
+ describe('layers', () => {
68
+ test('layer composition works through dispatch', async () => {
69
+ const log: string[] = [];
70
+ const layer: Layer = {
71
+ name: 'test-layer',
72
+ wrap(_trail, impl) {
73
+ return async (input, ctx) => {
74
+ log.push('before');
75
+ const r = await impl(input, ctx);
76
+ log.push('after');
77
+ return r;
78
+ };
79
+ },
80
+ };
81
+
82
+ const result = await dispatch(
83
+ testTopo,
84
+ 'echo',
85
+ { value: 'x' },
86
+ { layers: [layer] }
87
+ );
88
+
89
+ expect(result.isOk()).toBe(true);
90
+ expect(log).toEqual(['before', 'after']);
91
+ });
92
+ });
93
+
94
+ describe('context', () => {
95
+ test('context overrides work through dispatch', async () => {
96
+ let capturedCtx: TrailContext | undefined;
97
+ const ctxTrail = trail('ctx-dispatch-test', {
98
+ input: z.object({}),
99
+ run: (_input, ctx) => {
100
+ capturedCtx = ctx;
101
+ return Result.ok(null);
102
+ },
103
+ });
104
+
105
+ const ctxTopo = topo('ctx-test', { ctxTrail });
106
+ await dispatch(
107
+ ctxTopo,
108
+ 'ctx-dispatch-test',
109
+ {},
110
+ { ctx: { requestId: 'override-id' } }
111
+ );
112
+
113
+ expect(capturedCtx?.requestId).toBe('override-id');
114
+ });
115
+
116
+ test('createContext factory works through dispatch', async () => {
117
+ let capturedCtx: TrailContext | undefined;
118
+ const ctxTrail = trail('factory-dispatch-test', {
119
+ input: z.object({}),
120
+ run: (_input, ctx) => {
121
+ capturedCtx = ctx;
122
+ return Result.ok(null);
123
+ },
124
+ });
125
+
126
+ const ctxTopo = topo('factory-test', { ctxTrail });
127
+ const customCtx: TrailContext = {
128
+ cwd: '/custom',
129
+ requestId: 'factory-id',
130
+ signal: new AbortController().signal,
131
+ };
132
+
133
+ await dispatch(
134
+ ctxTopo,
135
+ 'factory-dispatch-test',
136
+ {},
137
+ { createContext: () => customCtx }
138
+ );
139
+
140
+ expect(capturedCtx?.requestId).toBe('factory-id');
141
+ expect(capturedCtx?.cwd).toBe('/custom');
142
+ });
143
+ });
144
+
145
+ describe('error handling', () => {
146
+ test('never throws — exceptions become InternalError', async () => {
147
+ const result = await dispatch(testTopo, 'throws', {});
148
+
149
+ expect(result.isErr()).toBe(true);
150
+ expect(result.error).toBeInstanceOf(InternalError);
151
+ expect(result.error.message).toBe('kaboom');
152
+ });
153
+ });
154
+ });
@@ -0,0 +1,185 @@
1
+ /* oxlint-disable require-await -- trail implementations satisfy async interface without awaiting */
2
+ import { describe, test, expect } from 'bun:test';
3
+
4
+ import { z } from 'zod';
5
+
6
+ import { InternalError, ValidationError } from '../errors';
7
+ import { executeTrail } from '../execute';
8
+ import type { Layer } from '../layer';
9
+ import { Result } from '../result';
10
+ import { trail } from '../trail';
11
+ import type { TrailContext } from '../types';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Fixtures
15
+ // ---------------------------------------------------------------------------
16
+
17
+ const echoTrail = trail('echo', {
18
+ input: z.object({ value: z.string() }),
19
+ output: z.object({ value: z.string() }),
20
+ run: (input) => Result.ok({ value: input.value }),
21
+ });
22
+
23
+ const failingTrail = trail('fails', {
24
+ input: z.object({}),
25
+ run: () => Result.err(new ValidationError('bad input')),
26
+ });
27
+
28
+ const throwingTrail = trail('throws', {
29
+ input: z.object({}),
30
+ run: () => {
31
+ throw new Error('kaboom');
32
+ },
33
+ });
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Tests
37
+ // ---------------------------------------------------------------------------
38
+
39
+ describe('executeTrail', () => {
40
+ describe('happy path', () => {
41
+ test('validates input and executes trail', async () => {
42
+ const result = await executeTrail(echoTrail, { value: 'hello' });
43
+
44
+ expect(result.isOk()).toBe(true);
45
+ expect(result.unwrap()).toEqual({ value: 'hello' });
46
+ });
47
+ });
48
+
49
+ describe('validation', () => {
50
+ test('returns validation error for invalid input', async () => {
51
+ const result = await executeTrail(echoTrail, { value: 42 });
52
+
53
+ expect(result.isErr()).toBe(true);
54
+ expect(result.error).toBeInstanceOf(ValidationError);
55
+ });
56
+ });
57
+
58
+ describe('layers', () => {
59
+ test('composes layers around execution', async () => {
60
+ const log: string[] = [];
61
+ const layer: Layer = {
62
+ name: 'test-layer',
63
+ wrap(_trail, impl) {
64
+ return async (input, ctx) => {
65
+ log.push('before');
66
+ const r = await impl(input, ctx);
67
+ log.push('after');
68
+ return r;
69
+ };
70
+ },
71
+ };
72
+
73
+ const result = await executeTrail(
74
+ echoTrail,
75
+ { value: 'x' },
76
+ { layers: [layer] }
77
+ );
78
+
79
+ expect(result.isOk()).toBe(true);
80
+ expect(log).toEqual(['before', 'after']);
81
+ });
82
+ });
83
+
84
+ describe('context', () => {
85
+ test('accepts context overrides', async () => {
86
+ let capturedCtx: TrailContext | undefined;
87
+ const ctxTrail = trail('ctx-test', {
88
+ input: z.object({}),
89
+ run: (_input, ctx) => {
90
+ capturedCtx = ctx;
91
+ return Result.ok(null);
92
+ },
93
+ });
94
+
95
+ await executeTrail(ctxTrail, {}, { ctx: { requestId: 'override-id' } });
96
+
97
+ expect(capturedCtx?.requestId).toBe('override-id');
98
+ });
99
+
100
+ test('accepts signal override', async () => {
101
+ let capturedSignal: AbortSignal | undefined;
102
+ const sigTrail = trail('sig-test', {
103
+ input: z.object({}),
104
+ run: (_input, ctx) => {
105
+ capturedSignal = ctx.signal;
106
+ return Result.ok(null);
107
+ },
108
+ });
109
+
110
+ const signal = AbortSignal.timeout(9999);
111
+ await executeTrail(sigTrail, {}, { signal });
112
+
113
+ expect(capturedSignal).toBe(signal);
114
+ });
115
+
116
+ test('accepts context factory', async () => {
117
+ let capturedCtx: TrailContext | undefined;
118
+ const ctxTrail = trail('factory-test', {
119
+ input: z.object({}),
120
+ run: (_input, ctx) => {
121
+ capturedCtx = ctx;
122
+ return Result.ok(null);
123
+ },
124
+ });
125
+
126
+ const customCtx: TrailContext = {
127
+ cwd: '/custom',
128
+ requestId: 'factory-id',
129
+ signal: new AbortController().signal,
130
+ };
131
+
132
+ await executeTrail(ctxTrail, {}, { createContext: () => customCtx });
133
+
134
+ expect(capturedCtx?.requestId).toBe('factory-id');
135
+ expect(capturedCtx?.cwd).toBe('/custom');
136
+ });
137
+
138
+ test('context factory + ctx overrides merge correctly', async () => {
139
+ let capturedCtx: TrailContext | undefined;
140
+ const ctxTrail = trail('merge-test', {
141
+ input: z.object({}),
142
+ run: (_input, ctx) => {
143
+ capturedCtx = ctx;
144
+ return Result.ok(null);
145
+ },
146
+ });
147
+
148
+ const baseCtx: TrailContext = {
149
+ cwd: '/factory',
150
+ requestId: 'factory-id',
151
+ signal: new AbortController().signal,
152
+ };
153
+
154
+ await executeTrail(
155
+ ctxTrail,
156
+ {},
157
+ {
158
+ createContext: () => baseCtx,
159
+ ctx: { requestId: 'overridden-id' },
160
+ }
161
+ );
162
+
163
+ expect(capturedCtx?.requestId).toBe('overridden-id');
164
+ expect(capturedCtx?.cwd).toBe('/factory');
165
+ });
166
+ });
167
+
168
+ describe('error handling', () => {
169
+ test('propagates Result.err from run function', async () => {
170
+ const result = await executeTrail(failingTrail, {});
171
+
172
+ expect(result.isErr()).toBe(true);
173
+ expect(result.error).toBeInstanceOf(ValidationError);
174
+ expect(result.error.message).toBe('bad input');
175
+ });
176
+
177
+ test('catches thrown exceptions and returns InternalError', async () => {
178
+ const result = await executeTrail(throwingTrail, {});
179
+
180
+ expect(result.isErr()).toBe(true);
181
+ expect(result.error).toBeInstanceOf(InternalError);
182
+ expect(result.error.message).toBe('kaboom');
183
+ });
184
+ });
185
+ });
@@ -116,6 +116,31 @@ describe('topo', () => {
116
116
  });
117
117
  });
118
118
 
119
+ // ---------------------------------------------------------------------------
120
+ // topo accessors
121
+ // ---------------------------------------------------------------------------
122
+
123
+ describe('topo accessors', () => {
124
+ test('ids() returns all trail IDs', () => {
125
+ const a = mockTrail('alpha');
126
+ const b = mockTrail('beta');
127
+ const app = topo('test', { a, b });
128
+ expect(app.ids().toSorted()).toEqual(['alpha', 'beta']);
129
+ });
130
+
131
+ test('count returns number of trails', () => {
132
+ const a = mockTrail('alpha');
133
+ const app = topo('test', { a });
134
+ expect(app.count).toBe(1);
135
+ });
136
+
137
+ test('empty topo has zero count and empty ids', () => {
138
+ const app = topo('empty');
139
+ expect(app.count).toBe(0);
140
+ expect(app.ids()).toEqual([]);
141
+ });
142
+ });
143
+
119
144
  // ---------------------------------------------------------------------------
120
145
  // Topo
121
146
  // ---------------------------------------------------------------------------
@@ -4,7 +4,7 @@ import { z } from 'zod';
4
4
 
5
5
  import { Result } from '../result';
6
6
  import { trail } from '../trail';
7
- import type { TrailInput, TrailOutput } from '../type-utils';
7
+ import type { TrailInput, TrailOutput, TrailResult } from '../type-utils';
8
8
  import { inputOf, outputOf } from '../type-utils';
9
9
 
10
10
  const greetTrail = trail('greet', {
@@ -67,4 +67,24 @@ describe('type-utils', () => {
67
67
  expect(_output.message).toBe('hello');
68
68
  });
69
69
  });
70
+
71
+ describe('TrailResult', () => {
72
+ test('extracts Result<Output, Error> from a trail', () => {
73
+ const t = trail('test.result', {
74
+ input: z.object({ q: z.string() }),
75
+ output: z.object({ answer: z.string() }),
76
+ run: (input) => Result.ok({ answer: input.q }),
77
+ });
78
+
79
+ type Expected = Result<{ answer: string }, Error>;
80
+ type Actual = TrailResult<typeof t>;
81
+
82
+ // Compile-time check: assignment works in both directions
83
+ const _check1: Expected = {} as Actual;
84
+ const _check2: Actual = {} as Expected;
85
+
86
+ // Runtime: type exists and is usable
87
+ expect(true).toBe(true);
88
+ });
89
+ });
70
90
  });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Headless trail execution — the "no-surface" surface.
3
+ *
4
+ * Looks up a trail by ID in a topo, then delegates to `executeTrail`.
5
+ * Returns a `Result` and never throws.
6
+ */
7
+
8
+ import type { Topo } from './topo.js';
9
+ import type { TrailContext } from './types.js';
10
+ import type { Layer } from './layer.js';
11
+ import { executeTrail } from './execute.js';
12
+ import { NotFoundError } from './errors.js';
13
+ import { Result } from './result.js';
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Options
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /** Options forwarded to `executeTrail` from `dispatch`. */
20
+ export interface DispatchOptions {
21
+ /** Partial context overrides merged on top of the base context. */
22
+ readonly ctx?: Partial<TrailContext> | undefined;
23
+ /** AbortSignal override (takes final precedence over ctx and factory). */
24
+ readonly signal?: AbortSignal | undefined;
25
+ /** Layers to compose around the implementation. */
26
+ readonly layers?: readonly Layer[] | undefined;
27
+ /** Factory that produces a base TrailContext (takes precedence over defaults). */
28
+ readonly createContext?:
29
+ | (() => TrailContext | Promise<TrailContext>)
30
+ | undefined;
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // dispatch()
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /**
38
+ * Execute a trail by ID from a topo without mounting a surface.
39
+ *
40
+ * Resolves the trail from the topo, then runs it through the standard
41
+ * `executeTrail` pipeline. Returns `Result.err(NotFoundError)` if the
42
+ * trail ID is not registered. Never throws — unexpected exceptions are
43
+ * returned as `Result.err(InternalError)`.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const result = await dispatch(myTopo, 'greet', { name: 'Alice' });
48
+ * if (result.isOk()) console.log(result.value);
49
+ * ```
50
+ */
51
+ export const dispatch = (
52
+ topo: Topo,
53
+ id: string,
54
+ input: unknown,
55
+ options?: DispatchOptions
56
+ ): Promise<Result<unknown, Error>> => {
57
+ const trail = topo.get(id);
58
+ if (trail === undefined) {
59
+ return Promise.resolve(
60
+ Result.err(
61
+ new NotFoundError(`Trail "${id}" not found in topo "${topo.name}"`)
62
+ )
63
+ );
64
+ }
65
+ return executeTrail(trail, input, options);
66
+ };
package/src/execute.ts ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Centralized trail execution pipeline.
3
+ *
4
+ * Validates input, builds context, composes layers, and runs the
5
+ * implementation. Surfaces (CLI, MCP, HTTP) delegate here instead
6
+ * of reimplementing the pipeline.
7
+ */
8
+
9
+ import type { AnyTrail } from './trail.js';
10
+ import type { Layer } from './layer.js';
11
+ import type { TrailContext } from './types.js';
12
+
13
+ import { composeLayers } from './layer.js';
14
+ import { createTrailContext } from './context.js';
15
+ import { InternalError } from './errors.js';
16
+ import { Result } from './result.js';
17
+ import { validateInput } from './validation.js';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Options
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** Options for executeTrail. */
24
+ export interface ExecuteTrailOptions {
25
+ /** Partial context overrides merged on top of the base context. */
26
+ readonly ctx?: Partial<TrailContext> | undefined;
27
+ /** AbortSignal override (takes final precedence over ctx and factory). */
28
+ readonly signal?: AbortSignal | undefined;
29
+ /** Layers to compose around the implementation. */
30
+ readonly layers?: readonly Layer[] | undefined;
31
+ /** Factory that produces a base TrailContext (takes precedence over defaults). */
32
+ readonly createContext?:
33
+ | (() => TrailContext | Promise<TrailContext>)
34
+ | undefined;
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Context resolution
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /**
42
+ * Build a TrailContext from options.
43
+ *
44
+ * Resolution order:
45
+ * 1. Factory (`createContext`) or `createTrailContext()` defaults.
46
+ * 2. Partial `ctx` overrides merged on top.
47
+ * 3. `signal` override takes final precedence.
48
+ */
49
+ const resolveContext = async (
50
+ options?: ExecuteTrailOptions
51
+ ): Promise<TrailContext> => {
52
+ const base = options?.createContext
53
+ ? await options.createContext()
54
+ : createTrailContext();
55
+ const withOverrides = options?.ctx ? { ...base, ...options.ctx } : base;
56
+ return options?.signal
57
+ ? { ...withOverrides, signal: options.signal }
58
+ : withOverrides;
59
+ };
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Pipeline
63
+ // ---------------------------------------------------------------------------
64
+
65
+ /**
66
+ * Execute a trail through the standard validate-context-layers-run pipeline.
67
+ *
68
+ * The function never throws -- unexpected exceptions are caught and
69
+ * returned as `Result.err(InternalError)`.
70
+ */
71
+ export const executeTrail = async (
72
+ trail: AnyTrail,
73
+ rawInput: unknown,
74
+ options?: ExecuteTrailOptions
75
+ ): Promise<Result<unknown, Error>> => {
76
+ try {
77
+ const validated = validateInput(trail.input, rawInput);
78
+ if (validated.isErr()) {
79
+ return validated;
80
+ }
81
+
82
+ const ctx = await resolveContext(options);
83
+ const layers = options?.layers ?? [];
84
+ const impl = composeLayers([...layers], trail, trail.run);
85
+ return await impl(validated.value, ctx);
86
+ } catch (error: unknown) {
87
+ const message = error instanceof Error ? error.message : String(error);
88
+ return Result.err(new InternalError(message));
89
+ }
90
+ };
package/src/index.ts CHANGED
@@ -51,7 +51,7 @@ export type {
51
51
  } from './trail.js';
52
52
 
53
53
  // Type utilities
54
- export type { TrailInput, TrailOutput } from './type-utils.js';
54
+ export type { TrailInput, TrailOutput, TrailResult } from './type-utils.js';
55
55
  export { inputOf, outputOf } from './type-utils.js';
56
56
 
57
57
  // Event
@@ -87,6 +87,14 @@ export type {
87
87
  export { deriveFields } from './derive.js';
88
88
  export type { Field, FieldOverride } from './derive.js';
89
89
 
90
+ // Execute
91
+ export { executeTrail } from './execute.js';
92
+ export type { ExecuteTrailOptions } from './execute.js';
93
+
94
+ // Dispatch
95
+ export { dispatch } from './dispatch.js';
96
+ export type { DispatchOptions } from './dispatch.js';
97
+
90
98
  // Validation
91
99
  export {
92
100
  validateInput,
package/src/topo.ts CHANGED
@@ -14,8 +14,10 @@ export interface Topo {
14
14
  readonly name: string;
15
15
  readonly trails: ReadonlyMap<string, AnyTrail>;
16
16
  readonly events: ReadonlyMap<string, AnyEvent>;
17
+ readonly count: number;
17
18
  get(id: string): AnyTrail | undefined;
18
19
  has(id: string): boolean;
20
+ ids(): string[];
19
21
  list(): AnyTrail[];
20
22
  listEvents(): AnyEvent[];
21
23
  }
@@ -43,6 +45,7 @@ const createTopo = (
43
45
  trails: ReadonlyMap<string, AnyTrail>,
44
46
  events: ReadonlyMap<string, AnyEvent>
45
47
  ): Topo => ({
48
+ count: trails.size,
46
49
  events,
47
50
  get(id: string): AnyTrail | undefined {
48
51
  return trails.get(id);
@@ -51,6 +54,10 @@ const createTopo = (
51
54
  return trails.has(id);
52
55
  },
53
56
 
57
+ ids(): string[] {
58
+ return [...trails.keys()];
59
+ },
60
+
54
61
  list(): AnyTrail[] {
55
62
  return [...trails.values()];
56
63
  },
package/src/type-utils.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * Type utilities for extracting input/output types from trails.
3
3
  */
4
4
 
5
+ import type { Result } from './result.js';
5
6
  import type { AnyTrail, Trail } from './trail.js';
6
7
 
7
8
  // ---------------------------------------------------------------------------
@@ -18,6 +19,17 @@ export type TrailInput<T extends AnyTrail> =
18
19
  export type TrailOutput<T extends AnyTrail> =
19
20
  T extends Trail<any, infer O> ? O : never;
20
21
 
22
+ /**
23
+ * Extracts the full `Result<Output, Error>` type from a trail definition.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * type SearchResult = TrailResult<typeof searchTrail>;
28
+ * // Result<{ results: Item[]; count: number }, Error>
29
+ * ```
30
+ */
31
+ export type TrailResult<T extends AnyTrail> = Result<TrailOutput<T>, Error>;
32
+
21
33
  /* oxlint-enable no-explicit-any */
22
34
 
23
35
  // ---------------------------------------------------------------------------
@@ -1 +1 @@
1
- {"root":["./src/adapters.ts","./src/blob-ref.ts","./src/branded.ts","./src/collections.ts","./src/context.ts","./src/derive.ts","./src/errors.ts","./src/event.ts","./src/fetch.ts","./src/guards.ts","./src/health.ts","./src/index.ts","./src/job.ts","./src/layer.ts","./src/path-security.ts","./src/resilience.ts","./src/result.ts","./src/serialization.ts","./src/topo.ts","./src/trail.ts","./src/type-utils.ts","./src/types.ts","./src/validate-topo.ts","./src/validation.ts","./src/workspace.ts","./src/patterns/bulk.ts","./src/patterns/change.ts","./src/patterns/date-range.ts","./src/patterns/index.ts","./src/patterns/pagination.ts","./src/patterns/progress.ts","./src/patterns/sorting.ts","./src/patterns/status.ts","./src/patterns/timestamps.ts","./src/redaction/index.ts","./src/redaction/patterns.ts","./src/redaction/redactor.ts"],"version":"5.9.3"}
1
+ {"root":["./src/adapters.ts","./src/blob-ref.ts","./src/branded.ts","./src/collections.ts","./src/context.ts","./src/derive.ts","./src/dispatch.ts","./src/errors.ts","./src/event.ts","./src/execute.ts","./src/fetch.ts","./src/guards.ts","./src/health.ts","./src/index.ts","./src/job.ts","./src/layer.ts","./src/path-security.ts","./src/resilience.ts","./src/result.ts","./src/serialization.ts","./src/topo.ts","./src/trail.ts","./src/type-utils.ts","./src/types.ts","./src/validate-topo.ts","./src/validation.ts","./src/workspace.ts","./src/patterns/bulk.ts","./src/patterns/change.ts","./src/patterns/date-range.ts","./src/patterns/index.ts","./src/patterns/pagination.ts","./src/patterns/progress.ts","./src/patterns/sorting.ts","./src/patterns/status.ts","./src/patterns/timestamps.ts","./src/redaction/index.ts","./src/redaction/patterns.ts","./src/redaction/redactor.ts"],"version":"5.9.3"}