@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.
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +22 -0
- package/README.md +36 -0
- package/dist/dispatch.d.ts +37 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +34 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/execute.d.ts +30 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +58 -0
- package/dist/execute.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/topo.d.ts +2 -0
- package/dist/topo.d.ts.map +1 -1
- package/dist/topo.js +4 -0
- package/dist/topo.js.map +1 -1
- package/dist/type-utils.d.ts +11 -0
- package/dist/type-utils.d.ts.map +1 -1
- package/dist/type-utils.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/dispatch.test.ts +154 -0
- package/src/__tests__/execute.test.ts +185 -0
- package/src/__tests__/topo.test.ts +25 -0
- package/src/__tests__/type-utils.test.ts +21 -1
- package/src/dispatch.ts +66 -0
- package/src/execute.ts +90 -0
- package/src/index.ts +9 -1
- package/src/topo.ts +7 -0
- package/src/type-utils.ts +12 -0
- package/tsconfig.tsbuildinfo +1 -1
package/.turbo/turbo-lint.log
CHANGED
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"}
|
package/dist/dispatch.js
ADDED
|
@@ -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"}
|
package/dist/execute.js
ADDED
|
@@ -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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,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,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,YAAY,EACV,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,MAAM,EACN,OAAO,GACR,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EACV,QAAQ,EACR,MAAM,EACN,KAAK,EACL,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,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,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,YAAY,EACV,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,MAAM,EACN,OAAO,GACR,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EACV,QAAQ,EACR,MAAM,EACN,KAAK,EACL,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGxC,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG9D,YAAY,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGxD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGxD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrD,OAAO,EACL,aAAa,EACb,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtE,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EACL,KAAK,EACL,WAAW,EACX,WAAW,EACX,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKpD,OAAO,EACL,KAAK,EACL,OAAO,EACP,IAAI,EACJ,KAAK,EACL,cAAc,EACd,WAAW,EACX,OAAO,EACP,MAAM,GACP,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,OAAO,EACP,IAAI,EACJ,KAAK,EACL,cAAc,EACd,WAAW,GACZ,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG7E,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACzD,YAAY,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,KAAK,EACL,MAAM,EACN,OAAO,EACP,MAAM,EACN,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,aAAa,EACb,WAAW,EACX,QAAQ,EACR,UAAU,GACX,MAAM,kBAAkB,CAAC"}
|
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
|
}
|
package/dist/topo.d.ts.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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"}
|
package/dist/type-utils.d.ts
CHANGED
|
@@ -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. */
|
package/dist/type-utils.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/type-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-utils.js","sourceRoot":"","sources":["../src/type-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
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
|
@@ -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
|
});
|
package/src/dispatch.ts
ADDED
|
@@ -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
|
// ---------------------------------------------------------------------------
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -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"}
|