@ontrails/core 1.0.0-beta.13 → 1.0.0-beta.15
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 +17 -0
- package/README.md +19 -14
- package/dist/branded.d.ts +1 -1
- package/dist/branded.d.ts.map +1 -1
- package/dist/branded.js +1 -1
- package/dist/branded.js.map +1 -1
- package/dist/context.d.ts +13 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +19 -3
- package/dist/context.js.map +1 -1
- package/dist/contour.d.ts +81 -0
- package/dist/contour.d.ts.map +1 -0
- package/dist/contour.js +176 -0
- package/dist/contour.js.map +1 -0
- package/dist/cross-schema.d.ts +18 -0
- package/dist/cross-schema.d.ts.map +1 -0
- package/dist/cross-schema.js +29 -0
- package/dist/cross-schema.js.map +1 -0
- package/dist/derive.d.ts +6 -0
- package/dist/derive.d.ts.map +1 -1
- package/dist/derive.js +29 -5
- package/dist/derive.js.map +1 -1
- package/dist/draft.d.ts +28 -0
- package/dist/draft.d.ts.map +1 -0
- package/dist/draft.js +157 -0
- package/dist/draft.js.map +1 -0
- package/dist/errors.d.ts +62 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +44 -1
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +26 -9
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +441 -20
- package/dist/execute.js.map +1 -1
- package/dist/fire.d.ts +43 -0
- package/dist/fire.d.ts.map +1 -0
- package/dist/fire.js +185 -0
- package/dist/fire.js.map +1 -0
- package/dist/index.d.ts +33 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -12
- package/dist/index.js.map +1 -1
- package/dist/internal/cross-batch.d.ts +44 -0
- package/dist/internal/cross-batch.d.ts.map +1 -0
- package/dist/internal/cross-batch.js +58 -0
- package/dist/internal/cross-batch.js.map +1 -0
- package/dist/internal/fork-ctx.d.ts +41 -0
- package/dist/internal/fork-ctx.d.ts.map +1 -0
- package/dist/internal/fork-ctx.js +47 -0
- package/dist/internal/fork-ctx.js.map +1 -0
- package/dist/internal/signal-ref.d.ts +15 -0
- package/dist/internal/signal-ref.d.ts.map +1 -0
- package/dist/internal/signal-ref.js +41 -0
- package/dist/internal/signal-ref.js.map +1 -0
- package/dist/internal/topo-saves.d.ts +53 -0
- package/dist/internal/topo-saves.d.ts.map +1 -0
- package/dist/internal/topo-saves.js +449 -0
- package/dist/internal/topo-saves.js.map +1 -0
- package/dist/internal/topo-snapshots.d.ts +57 -0
- package/dist/internal/topo-snapshots.d.ts.map +1 -0
- package/dist/internal/topo-snapshots.js +372 -0
- package/dist/internal/topo-snapshots.js.map +1 -0
- package/dist/internal/topo-store-read.d.ts +70 -0
- package/dist/internal/topo-store-read.d.ts.map +1 -0
- package/dist/internal/topo-store-read.js +221 -0
- package/dist/internal/topo-store-read.js.map +1 -0
- package/dist/internal/topo-store.d.ts +26 -0
- package/dist/internal/topo-store.d.ts.map +1 -0
- package/dist/internal/topo-store.js +621 -0
- package/dist/internal/topo-store.js.map +1 -0
- package/dist/internal/tracing.d.ts +98 -0
- package/dist/internal/tracing.d.ts.map +1 -0
- package/dist/internal/tracing.js +102 -0
- package/dist/internal/tracing.js.map +1 -0
- package/dist/internal/trails-db.d.ts +16 -0
- package/dist/internal/trails-db.d.ts.map +1 -0
- package/dist/internal/trails-db.js +118 -0
- package/dist/internal/trails-db.js.map +1 -0
- package/dist/internal/zod-wrappers.d.ts +40 -0
- package/dist/internal/zod-wrappers.d.ts.map +1 -0
- package/dist/internal/zod-wrappers.js +59 -0
- package/dist/internal/zod-wrappers.js.map +1 -0
- package/dist/layer.d.ts +22 -5
- package/dist/layer.d.ts.map +1 -1
- package/dist/layer.js +20 -3
- package/dist/layer.js.map +1 -1
- package/dist/path-security.d.ts +1 -1
- package/dist/path-security.d.ts.map +1 -1
- package/dist/path-security.js +1 -1
- package/dist/path-security.js.map +1 -1
- package/dist/resilience.d.ts +1 -1
- package/dist/resilience.d.ts.map +1 -1
- package/dist/resilience.js +3 -3
- package/dist/resilience.js.map +1 -1
- package/dist/resource-config.d.ts +22 -0
- package/dist/resource-config.d.ts.map +1 -0
- package/dist/resource-config.js +210 -0
- package/dist/resource-config.js.map +1 -0
- package/dist/resource.d.ts +74 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +61 -0
- package/dist/resource.js.map +1 -0
- package/dist/run.d.ts +4 -3
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +5 -4
- package/dist/run.js.map +1 -1
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +2 -1
- package/dist/serialization.js.map +1 -1
- package/dist/store/accessor-protocol.d.ts +51 -0
- package/dist/store/accessor-protocol.d.ts.map +1 -0
- package/dist/store/accessor-protocol.js +2 -0
- package/dist/store/accessor-protocol.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/surface-filter.d.ts +13 -0
- package/dist/surface-filter.d.ts.map +1 -0
- package/dist/surface-filter.js +76 -0
- package/dist/surface-filter.js.map +1 -0
- package/dist/topo-store.d.ts +62 -0
- package/dist/topo-store.d.ts.map +1 -0
- package/dist/topo-store.js +319 -0
- package/dist/topo-store.js.map +1 -0
- package/dist/topo.d.ts +23 -9
- package/dist/topo.d.ts.map +1 -1
- package/dist/topo.js +175 -46
- package/dist/topo.js.map +1 -1
- package/dist/trail.d.ts +86 -19
- package/dist/trail.d.ts.map +1 -1
- package/dist/trail.js +59 -4
- package/dist/trail.js.map +1 -1
- package/dist/trails/derive-trail.d.ts +66 -0
- package/dist/trails/derive-trail.d.ts.map +1 -0
- package/dist/trails/derive-trail.js +378 -0
- package/dist/trails/derive-trail.js.map +1 -0
- package/dist/trails/index.d.ts +5 -0
- package/dist/trails/index.d.ts.map +1 -0
- package/dist/trails/index.js +3 -0
- package/dist/trails/index.js.map +1 -0
- package/dist/trails/ingest.d.ts +24 -0
- package/dist/trails/ingest.d.ts.map +1 -0
- package/dist/trails/ingest.js +63 -0
- package/dist/trails/ingest.js.map +1 -0
- package/dist/transport-error-map.d.ts +106 -0
- package/dist/transport-error-map.d.ts.map +1 -0
- package/dist/transport-error-map.js +24 -0
- package/dist/transport-error-map.js.map +1 -0
- package/dist/type-checks.test-d.d.ts +71 -0
- package/dist/type-checks.test-d.d.ts.map +1 -0
- package/dist/type-checks.test-d.js +12 -0
- package/dist/type-checks.test-d.js.map +1 -0
- package/dist/type-utils.d.ts +11 -2
- package/dist/type-utils.d.ts.map +1 -1
- package/dist/type-utils.js.map +1 -1
- package/dist/types.d.ts +113 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate-established-topo.d.ts +76 -0
- package/dist/validate-established-topo.d.ts.map +1 -0
- package/dist/validate-established-topo.js +43 -0
- package/dist/validate-established-topo.js.map +1 -0
- package/dist/validate-topo.d.ts.map +1 -1
- package/dist/validate-topo.js +27 -8
- package/dist/validate-topo.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +1 -1
- package/dist/workspace.js.map +1 -1
- package/package.json +10 -1
- package/src/__tests__/branded.test.ts +5 -5
- package/src/__tests__/context.test.ts +3 -3
- package/src/__tests__/contour.test.ts +263 -0
- package/src/__tests__/derive-trail.test.ts +668 -0
- package/src/__tests__/derive.test.ts +58 -1
- package/src/__tests__/errors.test.ts +118 -0
- package/src/__tests__/execute.test.ts +1179 -141
- package/src/__tests__/fire.test.ts +1056 -0
- package/src/__tests__/fork-ctx.test.ts +83 -0
- package/src/__tests__/ingest.test.ts +191 -0
- package/src/__tests__/{gate.test.ts → layer.test.ts} +21 -21
- package/src/__tests__/path-security.test.ts +7 -7
- package/src/__tests__/resilience.test.ts +8 -8
- package/src/__tests__/resource.test.ts +204 -0
- package/src/__tests__/run.test.ts +13 -13
- package/src/__tests__/service-config.test.ts +28 -32
- package/src/__tests__/surface-filter.test.ts +196 -0
- package/src/__tests__/topo-store-read.test.ts +256 -0
- package/src/__tests__/topo-store.test.ts +869 -0
- package/src/__tests__/topo.test.ts +350 -54
- package/src/__tests__/trail.test.ts +189 -24
- package/src/__tests__/trails-db.test.ts +198 -0
- package/src/__tests__/transport-error-map.test.ts +82 -0
- package/src/__tests__/type-utils.test.ts +29 -1
- package/src/__tests__/validate-topo.test.ts +305 -16
- package/src/__tests__/workspace.test.ts +6 -6
- package/src/__tests__/zod-wrappers.test.ts +62 -0
- package/src/branded.ts +1 -1
- package/src/context.ts +24 -4
- package/src/contour.ts +344 -0
- package/src/cross-schema.ts +36 -0
- package/src/derive.ts +39 -8
- package/src/draft.ts +338 -0
- package/src/errors.ts +62 -18
- package/src/execute.ts +840 -29
- package/src/fire.ts +295 -0
- package/src/index.ts +142 -26
- package/src/internal/cross-batch.ts +77 -0
- package/src/internal/fork-ctx.ts +69 -0
- package/src/internal/signal-ref.ts +87 -0
- package/src/internal/topo-snapshots.ts +517 -0
- package/src/internal/topo-store-read.ts +481 -0
- package/src/internal/topo-store.ts +1192 -0
- package/src/internal/tracing.ts +187 -0
- package/src/internal/trails-db.ts +187 -0
- package/src/internal/zod-wrappers.ts +78 -0
- package/src/layer.ts +50 -0
- package/src/path-security.ts +1 -1
- package/src/resilience.ts +3 -3
- package/src/{provision-config.ts → resource-config.ts} +109 -123
- package/src/resource.ts +155 -0
- package/src/run.ts +5 -4
- package/src/serialization.ts +2 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/surface-filter.ts +182 -0
- package/src/topo-store.ts +510 -0
- package/src/topo.ts +328 -57
- package/src/trail.ts +199 -31
- package/src/trails/derive-trail.ts +842 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +146 -0
- package/src/transport-error-map.ts +56 -0
- package/src/type-checks.test-d.ts +106 -0
- package/src/type-utils.ts +17 -2
- package/src/types.ts +148 -11
- package/src/validate-established-topo.ts +63 -0
- package/src/validate-topo.ts +37 -9
- package/src/workspace.ts +1 -1
- package/tsconfig.tests.json +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/adapters.d.ts +0 -39
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -2
- package/dist/adapters.js.map +0 -1
- package/dist/dispatch.d.ts +0 -27
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -34
- package/dist/dispatch.js.map +0 -1
- package/dist/event.d.ts +0 -8
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -7
- package/dist/event.js.map +0 -1
- package/dist/gate.d.ts +0 -17
- package/dist/gate.d.ts.map +0 -1
- package/dist/gate.js +0 -21
- package/dist/gate.js.map +0 -1
- package/dist/health.d.ts +0 -18
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -5
- package/dist/health.js.map +0 -1
- package/dist/job.d.ts +0 -24
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -17
- package/dist/job.js.map +0 -1
- package/dist/provision-config.d.ts +0 -22
- package/dist/provision-config.d.ts.map +0 -1
- package/dist/provision-config.js +0 -210
- package/dist/provision-config.js.map +0 -1
- package/dist/provision.d.ts +0 -71
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -56
- package/dist/provision.js.map +0 -1
- package/dist/service-config.d.ts +0 -22
- package/dist/service-config.d.ts.map +0 -1
- package/dist/service-config.js +0 -210
- package/dist/service-config.js.map +0 -1
- package/dist/service.d.ts +0 -71
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -56
- package/dist/service.js.map +0 -1
- package/src/__tests__/service.test.ts +0 -197
- package/src/event.ts +0 -15
- package/src/gate.ts +0 -44
- package/src/provision.ts +0 -148
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { deriveTrail } from './derive-trail.js';
|
|
2
|
+
export type {
|
|
3
|
+
DeriveTrailInput,
|
|
4
|
+
DeriveTrailOperation,
|
|
5
|
+
DeriveTrailOutput,
|
|
6
|
+
DeriveTrailSpec,
|
|
7
|
+
} from './derive-trail.js';
|
|
8
|
+
export { ingest } from './ingest.js';
|
|
9
|
+
export type { IngestOptions, IngestTransform } from './ingest.js';
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { InternalError } from '../errors.js';
|
|
4
|
+
import { composeLayers } from '../layer.js';
|
|
5
|
+
import type { Layer } from '../layer.js';
|
|
6
|
+
import { Result } from '../result.js';
|
|
7
|
+
import type { Signal } from '../signal.js';
|
|
8
|
+
import { trail } from '../trail.js';
|
|
9
|
+
import type { Trail, TrailExample, TrailSpec } from '../trail.js';
|
|
10
|
+
import type { TrailContext } from '../types.js';
|
|
11
|
+
|
|
12
|
+
type SchemaValue<TSchema extends z.ZodType> = z.output<TSchema>;
|
|
13
|
+
type SignalRef<TSignal> = string | Signal<TSignal>;
|
|
14
|
+
|
|
15
|
+
type ExampleBearingSchema<TSchema extends z.ZodType> = TSchema & {
|
|
16
|
+
readonly examples?: readonly Partial<SchemaValue<TSchema>>[] | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface IngestBaseOptions<TSchema extends z.ZodType, TSignal> extends Omit<
|
|
20
|
+
TrailSpec<SchemaValue<TSchema>, void>,
|
|
21
|
+
'blaze' | 'examples' | 'fires' | 'input' | 'intent' | 'output' | 'pattern'
|
|
22
|
+
> {
|
|
23
|
+
/** Override the derived trail id. Defaults to `${signal}.ingest`. */
|
|
24
|
+
readonly id?: string | undefined;
|
|
25
|
+
/** Validated external payload shape. */
|
|
26
|
+
readonly schema: TSchema;
|
|
27
|
+
/** Signal to emit after verification and optional transformation. */
|
|
28
|
+
readonly signal: SignalRef<TSignal>;
|
|
29
|
+
/** Optional per-trail verification layer, e.g. HMAC signature checks. */
|
|
30
|
+
readonly verify?: Layer | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type IngestTransform<TInput, TSignal> = (
|
|
34
|
+
payload: TInput,
|
|
35
|
+
ctx: TrailContext
|
|
36
|
+
) => TSignal | Promise<TSignal>;
|
|
37
|
+
|
|
38
|
+
export interface IngestOptions<
|
|
39
|
+
TSchema extends z.ZodType,
|
|
40
|
+
TSignal,
|
|
41
|
+
> extends IngestBaseOptions<TSchema, TSignal> {
|
|
42
|
+
readonly transform?:
|
|
43
|
+
| IngestTransform<SchemaValue<TSchema>, TSignal>
|
|
44
|
+
| undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const resolveSignalId = <TSignal>(signalRef: SignalRef<TSignal>): string =>
|
|
48
|
+
typeof signalRef === 'string' ? signalRef : signalRef.id;
|
|
49
|
+
|
|
50
|
+
const deriveExampleName = (signalId: string, index: number): string =>
|
|
51
|
+
`Ingest ${signalId} ${index + 1}`;
|
|
52
|
+
|
|
53
|
+
const deriveExamples = <TSchema extends z.ZodType>(
|
|
54
|
+
schema: ExampleBearingSchema<TSchema>,
|
|
55
|
+
signalId: string
|
|
56
|
+
): readonly TrailExample<SchemaValue<TSchema>, void>[] | undefined => {
|
|
57
|
+
const { examples } = schema;
|
|
58
|
+
if (examples === undefined || examples.length === 0) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Object.freeze(
|
|
63
|
+
examples.map((example, index) => ({
|
|
64
|
+
input: example,
|
|
65
|
+
name: deriveExampleName(signalId, index),
|
|
66
|
+
}))
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const createIngestBlaze =
|
|
71
|
+
<TSchema extends z.ZodType, TSignal>(
|
|
72
|
+
signalRef: SignalRef<TSignal>,
|
|
73
|
+
signalId: string,
|
|
74
|
+
trailId: string,
|
|
75
|
+
transform: IngestTransform<SchemaValue<TSchema>, TSignal> | undefined
|
|
76
|
+
) =>
|
|
77
|
+
async (
|
|
78
|
+
input: SchemaValue<TSchema>,
|
|
79
|
+
ctx: TrailContext
|
|
80
|
+
): Promise<Result<void, Error>> => {
|
|
81
|
+
if (ctx.fire === undefined) {
|
|
82
|
+
return Result.err(
|
|
83
|
+
new InternalError(
|
|
84
|
+
`ingest("${trailId}") requires topo-backed execution to emit "${signalId}"`
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const payload =
|
|
91
|
+
transform === undefined
|
|
92
|
+
? (input as TSignal)
|
|
93
|
+
: await transform(input, ctx);
|
|
94
|
+
const fired =
|
|
95
|
+
typeof signalRef === 'string'
|
|
96
|
+
? await ctx.fire(signalRef, payload as unknown)
|
|
97
|
+
: await ctx.fire(signalRef as Signal<unknown>, payload as unknown);
|
|
98
|
+
|
|
99
|
+
return fired.isErr() ? Result.err(fired.error) : Result.ok();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const message = `ingest("${trailId}"): ${error instanceof Error ? error.message : String(error)}`;
|
|
102
|
+
return Result.err(
|
|
103
|
+
error instanceof Error
|
|
104
|
+
? new InternalError(message, { cause: error })
|
|
105
|
+
: new InternalError(message)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const ingest = <
|
|
111
|
+
TSchema extends z.ZodType,
|
|
112
|
+
TSignal = SchemaValue<TSchema>,
|
|
113
|
+
>(
|
|
114
|
+
options: IngestOptions<TSchema, TSignal>
|
|
115
|
+
): Trail<SchemaValue<TSchema>, void> => {
|
|
116
|
+
const signalId = resolveSignalId(options.signal);
|
|
117
|
+
const id = options.id ?? `${signalId}.ingest`;
|
|
118
|
+
const { id: _id, schema, signal, transform, verify, ...trailSpec } = options;
|
|
119
|
+
const baseBlaze = createIngestBlaze<TSchema, TSignal>(
|
|
120
|
+
signal,
|
|
121
|
+
signalId,
|
|
122
|
+
id,
|
|
123
|
+
transform
|
|
124
|
+
);
|
|
125
|
+
const baseTrail = trail(id, {
|
|
126
|
+
...trailSpec,
|
|
127
|
+
blaze: baseBlaze,
|
|
128
|
+
examples: deriveExamples(schema as ExampleBearingSchema<TSchema>, signalId),
|
|
129
|
+
fires: [signal],
|
|
130
|
+
input: schema as z.ZodType<SchemaValue<TSchema>>,
|
|
131
|
+
intent: 'write',
|
|
132
|
+
output: z.void(),
|
|
133
|
+
pattern: 'ingest',
|
|
134
|
+
}) as Trail<SchemaValue<TSchema>, void>;
|
|
135
|
+
|
|
136
|
+
if (verify === undefined) {
|
|
137
|
+
return baseTrail;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Verification is a per-factory concern, so compose it locally instead of
|
|
141
|
+
// mutating runner-wide layer configuration.
|
|
142
|
+
return Object.freeze({
|
|
143
|
+
...baseTrail,
|
|
144
|
+
blaze: composeLayers([verify], baseTrail, baseTrail.blaze),
|
|
145
|
+
}) as Trail<SchemaValue<TSchema>, void>;
|
|
146
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ErrorCategory, TrailsError } from './errors.js';
|
|
2
|
+
import { exitCodeMap, jsonRpcCodeMap, statusCodeMap } from './errors.js';
|
|
3
|
+
|
|
4
|
+
export const transportNames = ['cli', 'http', 'mcp'] as const;
|
|
5
|
+
|
|
6
|
+
export type TransportName = (typeof transportNames)[number];
|
|
7
|
+
|
|
8
|
+
export type TransportErrorMapper<T> = (error: TrailsError) => T;
|
|
9
|
+
|
|
10
|
+
export type TransportErrorMappings<T> = Record<ErrorCategory, T>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Union of every transport-specific error code emitted by {@link transportErrorMap}.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* Previously parameterised by transport (`TransportErrorCode<'cli'>` etc.), but
|
|
17
|
+
* the generic collapsed to `number` because the underlying maps were typed as
|
|
18
|
+
* `Record<ErrorCategory, number>`. With `as const satisfies` on the maps the
|
|
19
|
+
* per-transport literals are now observable, but TypeScript cannot narrow
|
|
20
|
+
* `transportErrorMap[transport][error.category]` through a generic
|
|
21
|
+
* `TTransport` without an unsound cast. The non-generic union honestly reflects
|
|
22
|
+
* what `mapTransportError` returns at the call site.
|
|
23
|
+
*/
|
|
24
|
+
export type TransportErrorCode =
|
|
25
|
+
(typeof transportErrorMap)[TransportName][ErrorCategory];
|
|
26
|
+
|
|
27
|
+
export const createTransportErrorMapper =
|
|
28
|
+
<T>(mappings: TransportErrorMappings<T>): TransportErrorMapper<T> =>
|
|
29
|
+
(error) =>
|
|
30
|
+
mappings[error.category];
|
|
31
|
+
|
|
32
|
+
export const transportErrorMap = {
|
|
33
|
+
cli: exitCodeMap,
|
|
34
|
+
http: statusCodeMap,
|
|
35
|
+
mcp: jsonRpcCodeMap,
|
|
36
|
+
} as const satisfies Record<TransportName, TransportErrorMappings<number>>;
|
|
37
|
+
|
|
38
|
+
export const transportErrorRegistry = {
|
|
39
|
+
cli: {
|
|
40
|
+
map: createTransportErrorMapper(transportErrorMap.cli),
|
|
41
|
+
values: transportErrorMap.cli,
|
|
42
|
+
},
|
|
43
|
+
http: {
|
|
44
|
+
map: createTransportErrorMapper(transportErrorMap.http),
|
|
45
|
+
values: transportErrorMap.http,
|
|
46
|
+
},
|
|
47
|
+
mcp: {
|
|
48
|
+
map: createTransportErrorMapper(transportErrorMap.mcp),
|
|
49
|
+
values: transportErrorMap.mcp,
|
|
50
|
+
},
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
export const mapTransportError = (
|
|
54
|
+
transport: TransportName,
|
|
55
|
+
error: TrailsError
|
|
56
|
+
): TransportErrorCode => transportErrorMap[transport][error.category];
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time type assertions for type-utils.
|
|
3
|
+
*
|
|
4
|
+
* This file lives in src/ (not __tests__/) so it is included in the
|
|
5
|
+
* typecheck pass. It contains no runtime code — only type-level
|
|
6
|
+
* assertions that fail the build when type inference regresses.
|
|
7
|
+
*
|
|
8
|
+
* Assertion types are exported to satisfy `noUnusedLocals` but are not
|
|
9
|
+
* re-exported from the package index.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Trail } from './trail.js';
|
|
13
|
+
import type { CrossInput, TrailInput } from './type-utils.js';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Helpers
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/** A trail with crossInput declared. */
|
|
20
|
+
type CrossTrail = Trail<
|
|
21
|
+
{ name: string },
|
|
22
|
+
{ id: string },
|
|
23
|
+
{ forkedFrom: string }
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
/** A trail without crossInput. */
|
|
27
|
+
type PlainTrail = Trail<{ name: string }, { id: string }>;
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// CrossInput<T> must include crossInput fields
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
type WithCrossInput = CrossInput<CrossTrail>;
|
|
34
|
+
|
|
35
|
+
// Must require both `name` AND `forkedFrom`.
|
|
36
|
+
// Before the fix, `forkedFrom` was erased.
|
|
37
|
+
type AssertMerged = WithCrossInput extends { name: string; forkedFrom: string }
|
|
38
|
+
? true
|
|
39
|
+
: false;
|
|
40
|
+
export type Merged = [AssertMerged] extends [true] ? 'pass' : never;
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// CrossInput<T> falls back to TrailInput<T> when no crossInput
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
type WithoutCrossInput = CrossInput<PlainTrail>;
|
|
47
|
+
type BaseInput = TrailInput<PlainTrail>;
|
|
48
|
+
|
|
49
|
+
// These should be mutually assignable (identical).
|
|
50
|
+
type AssertFallback1 = WithoutCrossInput extends BaseInput ? true : false;
|
|
51
|
+
type AssertFallback2 = BaseInput extends WithoutCrossInput ? true : false;
|
|
52
|
+
export type Fallback = [AssertFallback1, AssertFallback2] extends [true, true]
|
|
53
|
+
? 'pass'
|
|
54
|
+
: never;
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// BlazeInput: blaze receives crossInput fields when declared
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* When a trail has crossInput, the blaze's first parameter must include
|
|
62
|
+
* both the public input fields AND the crossInput fields. Before the fix,
|
|
63
|
+
* blaze was typed as Implementation<I, O>, losing the CI fields.
|
|
64
|
+
*/
|
|
65
|
+
type CrossBlazeParam = Parameters<CrossTrail['blaze']>[0];
|
|
66
|
+
|
|
67
|
+
type AssertBlazeHasCrossFields = CrossBlazeParam extends {
|
|
68
|
+
name: string;
|
|
69
|
+
forkedFrom: string;
|
|
70
|
+
}
|
|
71
|
+
? true
|
|
72
|
+
: false;
|
|
73
|
+
export type BlazeWithCross = [AssertBlazeHasCrossFields] extends [true]
|
|
74
|
+
? 'pass'
|
|
75
|
+
: never;
|
|
76
|
+
|
|
77
|
+
// Plain trail blaze should only receive the public input type.
|
|
78
|
+
type PlainBlazeParam = Parameters<PlainTrail['blaze']>[0];
|
|
79
|
+
|
|
80
|
+
type AssertPlainBlazeIsInput = PlainBlazeParam extends { name: string }
|
|
81
|
+
? true
|
|
82
|
+
: false;
|
|
83
|
+
type AssertPlainBlazeNoExtra = { name: string } extends PlainBlazeParam
|
|
84
|
+
? true
|
|
85
|
+
: false;
|
|
86
|
+
export type BlazeWithoutCross = [
|
|
87
|
+
AssertPlainBlazeIsInput,
|
|
88
|
+
AssertPlainBlazeNoExtra,
|
|
89
|
+
] extends [true, true]
|
|
90
|
+
? 'pass'
|
|
91
|
+
: never;
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// CI = never default preserves backward compat
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
// Trail<I, O> (two generics) should be assignable to Trail<I, O, never>
|
|
98
|
+
type AssertDefault =
|
|
99
|
+
Trail<{ x: number }, { y: number }> extends Trail<
|
|
100
|
+
{ x: number },
|
|
101
|
+
{ y: number },
|
|
102
|
+
never
|
|
103
|
+
>
|
|
104
|
+
? true
|
|
105
|
+
: false;
|
|
106
|
+
export type Default = [AssertDefault] extends [true] ? 'pass' : never;
|
package/src/type-utils.ts
CHANGED
|
@@ -13,11 +13,26 @@ import type { AnyTrail, Trail } from './trail.js';
|
|
|
13
13
|
|
|
14
14
|
/** Extract the input type from a Trail. */
|
|
15
15
|
export type TrailInput<T extends AnyTrail> =
|
|
16
|
-
T extends Trail<infer I, any> ? I : never;
|
|
16
|
+
T extends Trail<infer I, any, any> ? I : never;
|
|
17
17
|
|
|
18
18
|
/** Extract the output type from a Trail. */
|
|
19
19
|
export type TrailOutput<T extends AnyTrail> =
|
|
20
|
-
T extends Trail<any, infer O> ? O : never;
|
|
20
|
+
T extends Trail<any, infer O, any> ? O : never;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract the cross-callable input type from a trail.
|
|
24
|
+
*
|
|
25
|
+
* When a trail declares `crossInput`, callers via `ctx.cross()` must pass
|
|
26
|
+
* both the public input fields and the composition-only fields. This type
|
|
27
|
+
* merges both schemas so the compiler enforces the full shape at the call
|
|
28
|
+
* site. Falls back to plain `TrailInput<T>` when no `crossInput` exists.
|
|
29
|
+
*/
|
|
30
|
+
export type CrossInput<T extends AnyTrail> =
|
|
31
|
+
T extends Trail<infer I, any, infer CI>
|
|
32
|
+
? [CI] extends [never]
|
|
33
|
+
? I
|
|
34
|
+
: I & CI
|
|
35
|
+
: never;
|
|
21
36
|
|
|
22
37
|
/**
|
|
23
38
|
* Extracts the full `Result<Output, Error>` type from a trail definition.
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,64 @@
|
|
|
1
|
+
import type { TrailsError } from './errors.js';
|
|
1
2
|
import type { Result } from './result.js';
|
|
3
|
+
import type { Signal } from './signal.js';
|
|
4
|
+
import type { AnyTrail } from './trail.js';
|
|
5
|
+
import type { CrossInput, TrailOutput } from './type-utils.js';
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Detour
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
/** A recovery path that activates when a trail's blaze fails with a matching error. */
|
|
12
|
+
export interface Detour<Input, Output, TErr extends TrailsError = TrailsError> {
|
|
13
|
+
/* oxlint-disable-next-line no-explicit-any -- standard pattern for matching abstract+concrete class constructors */
|
|
14
|
+
readonly on: abstract new (...args: any[]) => TErr;
|
|
15
|
+
readonly maxAttempts?: number | undefined;
|
|
16
|
+
readonly recover: (
|
|
17
|
+
attempt: DetourAttempt<Input, TErr>,
|
|
18
|
+
ctx: TrailContext
|
|
19
|
+
) => Promise<Result<Output, TrailsError>>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Context passed to a detour's recover function on each attempt. */
|
|
23
|
+
export interface DetourAttempt<Input, TErr extends TrailsError = TrailsError> {
|
|
24
|
+
/** 1-indexed attempt number */
|
|
25
|
+
readonly attempt: number;
|
|
26
|
+
/** The matched error */
|
|
27
|
+
readonly error: TErr;
|
|
28
|
+
/** Original trail input */
|
|
29
|
+
readonly input: Input;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type CrossBatchCall<TTarget extends AnyTrail | string = AnyTrail | string> =
|
|
33
|
+
TTarget extends AnyTrail
|
|
34
|
+
? readonly [trail: TTarget, input: CrossInput<TTarget>]
|
|
35
|
+
: readonly [id: string, input: unknown];
|
|
36
|
+
|
|
37
|
+
type CrossBatchResult<TTarget extends AnyTrail | string> =
|
|
38
|
+
TTarget extends AnyTrail
|
|
39
|
+
? Result<TrailOutput<TTarget>, Error>
|
|
40
|
+
: Result<unknown, Error>;
|
|
41
|
+
|
|
42
|
+
type CrossBatchResults<TCalls extends readonly CrossBatchCall[]> = {
|
|
43
|
+
readonly [K in keyof TCalls]: TCalls[K] extends readonly [
|
|
44
|
+
infer TTarget,
|
|
45
|
+
unknown,
|
|
46
|
+
]
|
|
47
|
+
? TTarget extends AnyTrail | string
|
|
48
|
+
? CrossBatchResult<TTarget>
|
|
49
|
+
: never
|
|
50
|
+
: never;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Runtime options for batch `ctx.cross([...])` calls. */
|
|
54
|
+
export interface CrossBatchOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Maximum number of branches to execute concurrently.
|
|
57
|
+
*
|
|
58
|
+
* Omit for unbounded concurrency. `1` is equivalent to sequential execution.
|
|
59
|
+
*/
|
|
60
|
+
readonly concurrency?: number | undefined;
|
|
61
|
+
}
|
|
2
62
|
|
|
3
63
|
/**
|
|
4
64
|
* Trail implementation — sync or async.
|
|
@@ -11,17 +71,78 @@ export type Implementation<I, O> = (
|
|
|
11
71
|
ctx: TrailContext
|
|
12
72
|
) => Result<O, Error> | Promise<Result<O, Error>>;
|
|
13
73
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Invoke another trail — used for trail composition.
|
|
76
|
+
*
|
|
77
|
+
* Two call shapes:
|
|
78
|
+
*
|
|
79
|
+
* - **By trail object** (typed): `ctx.cross(showGist, { id })` — the compiler
|
|
80
|
+
* infers `I` and `O` from the trail's schemas, so the result is fully typed.
|
|
81
|
+
* - **By string id** (untyped escape hatch): `ctx.cross('gist.show', { id })`
|
|
82
|
+
* — returns `Result<O, Error>` where `O` defaults to `unknown`.
|
|
83
|
+
* - **By batch**: `ctx.cross([[showGist, { id }], ['audit.log', payload]])`
|
|
84
|
+
* — executes every crossing concurrently and resolves once all results are
|
|
85
|
+
* available. Result ordering always matches the input tuple ordering. Pass
|
|
86
|
+
* `{ concurrency: N }` as the second argument to limit how many branches
|
|
87
|
+
* run at once.
|
|
88
|
+
*/
|
|
89
|
+
export interface CrossFn {
|
|
90
|
+
<const TCalls extends readonly CrossBatchCall[]>(
|
|
91
|
+
calls: TCalls,
|
|
92
|
+
options?: CrossBatchOptions
|
|
93
|
+
): Promise<CrossBatchResults<TCalls>>;
|
|
94
|
+
<T extends AnyTrail>(
|
|
95
|
+
trail: T,
|
|
96
|
+
input: CrossInput<T>
|
|
97
|
+
): Promise<Result<TrailOutput<T>, Error>>;
|
|
98
|
+
<O = unknown>(id: string, input: unknown): Promise<Result<O, Error>>;
|
|
99
|
+
}
|
|
19
100
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Emit a signal — used for signal-driven activation.
|
|
103
|
+
*
|
|
104
|
+
* Fan-out to consumer trails (those with the signal in their `on:` array) is
|
|
105
|
+
* the framework's responsibility. Producers get `Result.ok(undefined)` unless
|
|
106
|
+
* the signal id is unknown or the payload fails schema validation. Consumer
|
|
107
|
+
* errors are logged but do not propagate back to the producer. Consumers fan
|
|
108
|
+
* out in parallel, each with its own derived context. Runtime cycle
|
|
109
|
+
* suppression is still signal-id-based against the current fire stack: it
|
|
110
|
+
* prevents re-entrant loops but can over-suppress legitimate diamond
|
|
111
|
+
* re-fires, with a debug breadcrumb and a warn emitted when suppression happens.
|
|
112
|
+
*
|
|
113
|
+
* Two call shapes are supported:
|
|
114
|
+
*
|
|
115
|
+
* - **By id** (base shape): `ctx.fire('order.placed', { ... })`. Matches
|
|
116
|
+
* the shape of `ctx.cross`; payload is typed as `unknown` and validated
|
|
117
|
+
* against the signal's schema at the fire boundary.
|
|
118
|
+
* - **By signal value** (progressive disclosure): `ctx.fire(orderPlaced, payload)`
|
|
119
|
+
* where `orderPlaced` is a `Signal<T>`. The compiler enforces that
|
|
120
|
+
* `payload` matches the signal's declared schema type at the call site,
|
|
121
|
+
* on top of the runtime validation.
|
|
122
|
+
*/
|
|
123
|
+
export interface FireFn {
|
|
124
|
+
<T>(signal: Signal<T>, payload: T): Promise<Result<void, Error>>;
|
|
125
|
+
(signalId: string, payload: unknown): Promise<Result<void, Error>>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Resolve a resource instance from the current trail context. */
|
|
129
|
+
export type ResourceLookup = <T = unknown>(
|
|
130
|
+
resourceOrId: { readonly id: string } | string
|
|
23
131
|
) => T;
|
|
24
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Wrap the execution of `fn` in a child trace span.
|
|
135
|
+
*
|
|
136
|
+
* Creates a nested span under the current trail's root trace record, times
|
|
137
|
+
* the callback, records success or failure (including error category), and
|
|
138
|
+
* writes the completed span to the registered sink. Errors thrown by `fn`
|
|
139
|
+
* are recorded on the span and then rethrown — tracing never swallows them.
|
|
140
|
+
*/
|
|
141
|
+
export type TraceFn = <T>(
|
|
142
|
+
label: string,
|
|
143
|
+
fn: () => T | Promise<T>
|
|
144
|
+
) => Promise<T>;
|
|
145
|
+
|
|
25
146
|
/** Callback for reporting progress from long-running trails */
|
|
26
147
|
export type ProgressCallback = (event: ProgressEvent) => void;
|
|
27
148
|
|
|
@@ -60,6 +181,12 @@ export interface TrailContext {
|
|
|
60
181
|
readonly requestId: string;
|
|
61
182
|
readonly abortSignal: AbortSignal;
|
|
62
183
|
readonly cross?: CrossFn | undefined;
|
|
184
|
+
/**
|
|
185
|
+
* Emit a signal by id. Fans out to every trail with the signal in its
|
|
186
|
+
* `on:` declaration. Bound by the runner that holds the topo (typically
|
|
187
|
+
* `run()`); undefined when a context is constructed without topo access.
|
|
188
|
+
*/
|
|
189
|
+
readonly fire?: FireFn | undefined;
|
|
63
190
|
readonly permit?: BasePermit;
|
|
64
191
|
readonly workspaceRoot?: string | undefined;
|
|
65
192
|
readonly logger?: Logger | undefined;
|
|
@@ -67,7 +194,16 @@ export interface TrailContext {
|
|
|
67
194
|
readonly cwd?: string | undefined;
|
|
68
195
|
readonly env?: Record<string, string | undefined> | undefined;
|
|
69
196
|
readonly extensions?: Readonly<Record<string, unknown>> | undefined;
|
|
70
|
-
readonly
|
|
197
|
+
readonly resource?: ResourceLookup | undefined;
|
|
198
|
+
/**
|
|
199
|
+
* Wrap a callback in a child trace span.
|
|
200
|
+
*
|
|
201
|
+
* Always present on contexts produced by `executeTrail` or
|
|
202
|
+
* `createTrailContext`. Optional on the interface so manually constructed
|
|
203
|
+
* contexts (tests, ad-hoc compositions) don't have to supply one — call
|
|
204
|
+
* sites tolerate `undefined` by falling back to a no-op passthrough.
|
|
205
|
+
*/
|
|
206
|
+
readonly trace?: TraceFn | undefined;
|
|
71
207
|
}
|
|
72
208
|
|
|
73
209
|
/**
|
|
@@ -82,6 +218,7 @@ export type PermitRequirement =
|
|
|
82
218
|
| 'public';
|
|
83
219
|
|
|
84
220
|
/** Input shape used to seed a runtime TrailContext before resolution. */
|
|
85
|
-
export type TrailContextInit = Omit<TrailContext, '
|
|
86
|
-
readonly
|
|
221
|
+
export type TrailContextInit = Omit<TrailContext, 'resource' | 'trace'> & {
|
|
222
|
+
readonly resource?: ResourceLookup | undefined;
|
|
223
|
+
readonly trace?: TraceFn | undefined;
|
|
87
224
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ValidationError } from './errors.js';
|
|
2
|
+
import { Result } from './result.js';
|
|
3
|
+
import type { Topo } from './topo.js';
|
|
4
|
+
import { validateDraftFreeTopo } from './draft.js';
|
|
5
|
+
import type { TopoIssue } from './validate-topo.js';
|
|
6
|
+
import { validateTopo } from './validate-topo.js';
|
|
7
|
+
|
|
8
|
+
const PROJECTION_BLOCKING_RULES = new Set([
|
|
9
|
+
'cross-cycle',
|
|
10
|
+
'cross-exists',
|
|
11
|
+
'no-self-cross',
|
|
12
|
+
'resource-exists',
|
|
13
|
+
'signal-origin-exists',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const keepProjectionBlockingIssues = (
|
|
17
|
+
result: ReturnType<typeof validateTopo>
|
|
18
|
+
) => {
|
|
19
|
+
if (result.isOk()) {
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const issues = (
|
|
24
|
+
result.error.context as { issues?: readonly TopoIssue[] } | undefined
|
|
25
|
+
)?.issues;
|
|
26
|
+
const remainingIssues = issues?.filter((issue) =>
|
|
27
|
+
PROJECTION_BLOCKING_RULES.has(issue.rule)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (remainingIssues === undefined || remainingIssues.length === 0) {
|
|
31
|
+
return Result.ok();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Result.err(
|
|
35
|
+
new ValidationError(
|
|
36
|
+
`Topo validation failed with ${remainingIssues.length} issue(s)`,
|
|
37
|
+
{
|
|
38
|
+
cause: result.error,
|
|
39
|
+
context: { issues: remainingIssues },
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validate that a topo is ready for established outputs.
|
|
47
|
+
*
|
|
48
|
+
* Established surfaces still require the authored graph to be structurally
|
|
49
|
+
* valid, and they must also reject any remaining draft state.
|
|
50
|
+
*/
|
|
51
|
+
export const validateEstablishedTopo = (topo: Topo) => {
|
|
52
|
+
const structural = keepProjectionBlockingIssues(validateTopo(topo));
|
|
53
|
+
if (structural.isErr()) {
|
|
54
|
+
return structural;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const established = validateDraftFreeTopo(topo);
|
|
58
|
+
if (established.isErr()) {
|
|
59
|
+
return established;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Result.ok();
|
|
63
|
+
};
|