@ontrails/core 1.0.0-beta.39 → 1.0.0-beta.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/README.md +10 -10
- package/package.json +1 -1
- package/src/activation-provenance.ts +5 -0
- package/src/activation-source-projection.ts +6 -0
- package/src/activation-source.ts +2 -0
- package/src/compose-batch.ts +1 -1
- package/src/draft.ts +21 -21
- package/src/{contour.ts → entity.ts} +72 -72
- package/src/execute.ts +15 -15
- package/src/fire.ts +5 -2
- package/src/index.ts +19 -17
- package/src/layer.ts +1 -1
- package/src/observe.ts +1 -1
- package/src/queue.ts +163 -0
- package/src/signal-ref.ts +30 -6
- package/src/store/accessor-protocol.ts +4 -4
- package/src/surface-overlay.ts +1 -1
- package/src/topo.ts +47 -47
- package/src/tracing.ts +1 -0
- package/src/trail.ts +92 -87
- package/src/trails/derive-trail.ts +202 -209
- package/src/trails/ingest.ts +18 -5
- package/src/trails-db.ts +1 -1
- package/src/type-utils.ts +1 -1
- package/src/types.ts +3 -3
- package/src/validate-established-topo.ts +7 -3
- package/src/validate-topo.ts +31 -15
package/src/trail.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isActivationEntrySpec,
|
|
12
12
|
isActivationSource,
|
|
13
13
|
} from './activation-source.js';
|
|
14
|
-
import type {
|
|
14
|
+
import type { AnyEntity } from './entity.js';
|
|
15
15
|
import type {
|
|
16
16
|
FieldOverride,
|
|
17
17
|
CliCommandPathInput,
|
|
@@ -24,6 +24,7 @@ import type { AnySignal } from './signal.js';
|
|
|
24
24
|
import {
|
|
25
25
|
createLateBoundSignalMarker,
|
|
26
26
|
getLateBoundSignalRef,
|
|
27
|
+
isBoundLateBoundSignal,
|
|
27
28
|
} from './signal-ref.js';
|
|
28
29
|
import type { TrailsError } from './errors.js';
|
|
29
30
|
import type {
|
|
@@ -75,18 +76,18 @@ export interface TrailExample<I, O> {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
// ---------------------------------------------------------------------------
|
|
78
|
-
//
|
|
79
|
+
// Implementation input — merges composeInput when declared
|
|
79
80
|
// ---------------------------------------------------------------------------
|
|
80
81
|
|
|
81
82
|
/**
|
|
82
|
-
* The input type received by a trail's
|
|
83
|
+
* The input type received by a trail's implementation function.
|
|
83
84
|
*
|
|
84
85
|
* When a trail declares `composeInput`, the runtime merges those fields into
|
|
85
|
-
* the input object before calling
|
|
86
|
+
* the input object before calling implementation. This type makes the compiler aware
|
|
86
87
|
* of the merged shape so developers can access composeInput fields without a
|
|
87
88
|
* cast. Falls back to plain `I` when `CI` is `never` (the default).
|
|
88
89
|
*/
|
|
89
|
-
export type
|
|
90
|
+
export type ImplementationInput<I, CI> = [CI] extends [never] ? I : I & CI;
|
|
90
91
|
|
|
91
92
|
type TrailRef = string | { readonly id: string };
|
|
92
93
|
|
|
@@ -105,13 +106,18 @@ type SchemaOwnedTrailSpec<
|
|
|
105
106
|
ComposeSchemaOutput<TComposeInputSchema>,
|
|
106
107
|
C
|
|
107
108
|
>,
|
|
108
|
-
|
|
109
|
+
| 'implementation'
|
|
110
|
+
| 'composeInput'
|
|
111
|
+
| 'detours'
|
|
112
|
+
| 'examples'
|
|
113
|
+
| 'input'
|
|
114
|
+
| 'versions'
|
|
109
115
|
> & {
|
|
110
|
-
/** Zod schema for validating caller input and materializing
|
|
116
|
+
/** Zod schema for validating caller input and materializing implementation input. */
|
|
111
117
|
readonly input: TInputSchema;
|
|
112
118
|
/** The pure function receives schema-materialized input after validation/defaults. */
|
|
113
|
-
readonly
|
|
114
|
-
|
|
119
|
+
readonly implementation: Implementation<
|
|
120
|
+
ImplementationInput<
|
|
115
121
|
z.output<TInputSchema>,
|
|
116
122
|
ComposeSchemaOutput<TComposeInputSchema>
|
|
117
123
|
>,
|
|
@@ -122,7 +128,7 @@ type SchemaOwnedTrailSpec<
|
|
|
122
128
|
readonly examples?:
|
|
123
129
|
| readonly TrailExample<z.input<TInputSchema>, O>[]
|
|
124
130
|
| undefined;
|
|
125
|
-
/** Recovery paths see the same materialized input as the
|
|
131
|
+
/** Recovery paths see the same materialized input as the implementation. */
|
|
126
132
|
readonly detours?:
|
|
127
133
|
| readonly Detour<z.output<TInputSchema>, O, TrailsError>[]
|
|
128
134
|
| undefined;
|
|
@@ -167,9 +173,9 @@ type LegacyOutputTrailSpec<
|
|
|
167
173
|
O,
|
|
168
174
|
CI,
|
|
169
175
|
C extends readonly TrailRef[] | undefined,
|
|
170
|
-
> = Omit<TrailSpec<I, O, CI, C>, '
|
|
171
|
-
readonly
|
|
172
|
-
|
|
176
|
+
> = Omit<TrailSpec<I, O, CI, C>, 'implementation' | 'output'> & {
|
|
177
|
+
readonly implementation: Implementation<
|
|
178
|
+
ImplementationInput<I, CI>,
|
|
173
179
|
NoInfer<O>,
|
|
174
180
|
ComposeContextFor<C>
|
|
175
181
|
>;
|
|
@@ -181,8 +187,12 @@ type LegacyOutputlessTrailSpec<
|
|
|
181
187
|
O,
|
|
182
188
|
CI,
|
|
183
189
|
C extends readonly TrailRef[] | undefined,
|
|
184
|
-
> = Omit<TrailSpec<I, O, CI, C>, '
|
|
185
|
-
readonly
|
|
190
|
+
> = Omit<TrailSpec<I, O, CI, C>, 'implementation' | 'output'> & {
|
|
191
|
+
readonly implementation: Implementation<
|
|
192
|
+
ImplementationInput<I, CI>,
|
|
193
|
+
O,
|
|
194
|
+
ComposeContextFor<C>
|
|
195
|
+
>;
|
|
186
196
|
readonly output?: undefined;
|
|
187
197
|
};
|
|
188
198
|
|
|
@@ -253,7 +263,7 @@ export interface TrailVersionRevisionEntry<
|
|
|
253
263
|
CurrentInput = unknown,
|
|
254
264
|
CurrentOutput = unknown,
|
|
255
265
|
> extends VersionEntry<VersionContract<VersionInput, VersionOutput>> {
|
|
256
|
-
readonly
|
|
266
|
+
readonly implementation?: never;
|
|
257
267
|
readonly composeInput?: never;
|
|
258
268
|
readonly composes?: never;
|
|
259
269
|
readonly detours?: never;
|
|
@@ -274,8 +284,8 @@ export interface TrailVersionForkEntry<
|
|
|
274
284
|
VersionOutput = unknown,
|
|
275
285
|
ComposeInput = never,
|
|
276
286
|
> extends VersionEntry<VersionContract<VersionInput, VersionOutput>> {
|
|
277
|
-
readonly
|
|
278
|
-
|
|
287
|
+
readonly implementation: Implementation<
|
|
288
|
+
ImplementationInput<VersionInput, ComposeInput>,
|
|
279
289
|
VersionOutput
|
|
280
290
|
>;
|
|
281
291
|
readonly composes?: readonly (string | AnyTrail)[] | undefined;
|
|
@@ -316,7 +326,7 @@ export type TrailVersions<
|
|
|
316
326
|
>;
|
|
317
327
|
|
|
318
328
|
/**
|
|
319
|
-
* Spec for {@link forkVersion}: a fork entry whose
|
|
329
|
+
* Spec for {@link forkVersion}: a fork entry whose implementation signature is owned
|
|
320
330
|
* by the entry's own schemas instead of falling back to `unknown`.
|
|
321
331
|
*/
|
|
322
332
|
export interface TrailVersionForkSpec<
|
|
@@ -324,9 +334,9 @@ export interface TrailVersionForkSpec<
|
|
|
324
334
|
TOutputSchema extends z.ZodType,
|
|
325
335
|
TComposeInputSchema extends z.ZodType | undefined = undefined,
|
|
326
336
|
> {
|
|
327
|
-
/** The historical
|
|
328
|
-
readonly
|
|
329
|
-
|
|
337
|
+
/** The historical implementation, typed by this entry's schemas. */
|
|
338
|
+
readonly implementation: Implementation<
|
|
339
|
+
ImplementationInput<
|
|
330
340
|
z.output<TInputSchema>,
|
|
331
341
|
ComposeSchemaOutput<TComposeInputSchema>
|
|
332
342
|
>,
|
|
@@ -351,15 +361,15 @@ export interface TrailVersionForkSpec<
|
|
|
351
361
|
}
|
|
352
362
|
|
|
353
363
|
/**
|
|
354
|
-
* Author a fork version entry with a
|
|
364
|
+
* Author a fork version entry with a implementation typed by the entry's own schemas.
|
|
355
365
|
*
|
|
356
|
-
* `TrailVersions` fixes every entry's generics to `unknown`, so a fork
|
|
366
|
+
* `TrailVersions` fixes every entry's generics to `unknown`, so a fork implementation
|
|
357
367
|
* written inline receives `unknown` input and authors end up re-parsing the
|
|
358
368
|
* already-validated value just to narrow it. This helper threads the entry's
|
|
359
|
-
* `input`/`output` schemas into the
|
|
369
|
+
* `input`/`output` schemas into the implementation signature and erases the generics
|
|
360
370
|
* on the way out. The erasure is sound because the fork pipeline validates
|
|
361
371
|
* raw input against this entry's own `input` schema before dispatching to
|
|
362
|
-
* the entry
|
|
372
|
+
* the entry implementation (see `createForkTrailVersion` in execute.ts).
|
|
363
373
|
*
|
|
364
374
|
* @example
|
|
365
375
|
* ```ts
|
|
@@ -371,7 +381,7 @@ export interface TrailVersionForkSpec<
|
|
|
371
381
|
* version: 2,
|
|
372
382
|
* versions: {
|
|
373
383
|
* 1: forkVersion({
|
|
374
|
-
*
|
|
384
|
+
* implementation: (input) =>
|
|
375
385
|
* // input is { name: string; weightOz: number } — no re-parse
|
|
376
386
|
* Result.ok({ id: input.name, weightOz: input.weightOz }),
|
|
377
387
|
* input: gearV1Input,
|
|
@@ -390,14 +400,14 @@ export const forkVersion = <
|
|
|
390
400
|
): TrailVersionForkEntry =>
|
|
391
401
|
// Erasing the per-entry generics narrows function parameters, which TS
|
|
392
402
|
// cannot express without a conversion. Safe: the fork pipeline re-validates
|
|
393
|
-
// input against `spec.input` before the
|
|
403
|
+
// input against `spec.input` before the implementation runs.
|
|
394
404
|
spec as unknown as TrailVersionForkEntry;
|
|
395
405
|
|
|
396
406
|
export const getTrailVersionEntryKind = (
|
|
397
407
|
entry: TrailVersionEntry
|
|
398
408
|
): TrailVersionEntryKind => {
|
|
399
409
|
const raw = entry as unknown as Record<string, unknown>;
|
|
400
|
-
return typeof raw['
|
|
410
|
+
return typeof raw['implementation'] === 'function' ? 'fork' : 'revision';
|
|
401
411
|
};
|
|
402
412
|
|
|
403
413
|
export const isArchivedTrailVersionEntry = (
|
|
@@ -455,7 +465,11 @@ export interface TrailSpec<
|
|
|
455
465
|
/** Zod schema for validating output (optional — some trails are fire-and-forget) */
|
|
456
466
|
readonly output?: z.ZodType<O> | undefined;
|
|
457
467
|
/** The pure function that does the work (sync or async authoring) */
|
|
458
|
-
readonly
|
|
468
|
+
readonly implementation: Implementation<
|
|
469
|
+
ImplementationInput<I, CI>,
|
|
470
|
+
O,
|
|
471
|
+
ComposeContextFor<C>
|
|
472
|
+
>;
|
|
459
473
|
/** Human-readable description */
|
|
460
474
|
readonly description?: string | undefined;
|
|
461
475
|
/** Declared operational shape for governance, derivation, and agent guidance. */
|
|
@@ -478,16 +492,16 @@ export interface TrailSpec<
|
|
|
478
492
|
readonly visibility?: TrailVisibility | undefined;
|
|
479
493
|
/** Arbitrary meta for tooling and filtering */
|
|
480
494
|
readonly meta?: Readonly<Record<string, unknown>> | undefined;
|
|
481
|
-
/** Recovery paths activated when
|
|
495
|
+
/** Recovery paths activated when implementation fails with a matching error class. */
|
|
482
496
|
readonly detours?: readonly Detour<I, O, TrailsError>[] | undefined;
|
|
483
497
|
/**
|
|
484
498
|
* Typed layers attached at trail scope.
|
|
485
499
|
*
|
|
486
500
|
* Layers declared here wrap this trail's implementation on every execution,
|
|
487
501
|
* regardless of which surface invokes it. The execution pipeline composes
|
|
488
|
-
* trail-scope layers innermost — closer to the
|
|
502
|
+
* trail-scope layers innermost — closer to the implementation than surface-scope
|
|
489
503
|
* or topo-scope layers — so the final order is
|
|
490
|
-
* `topo → surface → trail →
|
|
504
|
+
* `topo → surface → trail → implementation` (outermost-first).
|
|
491
505
|
*
|
|
492
506
|
* Layers are typed and inspectable. Omit `input` for surface-invisible
|
|
493
507
|
* wrappers that do not project any fields.
|
|
@@ -497,15 +511,15 @@ export interface TrailSpec<
|
|
|
497
511
|
readonly fields?: Readonly<Record<string, FieldOverride>> | undefined;
|
|
498
512
|
/** CLI projection metadata for canonical command path overrides and aliases. */
|
|
499
513
|
readonly cli?: CliCommandPathInput | TrailCliProjection | undefined;
|
|
500
|
-
/**
|
|
501
|
-
readonly
|
|
514
|
+
/** Entities this trail operates on. */
|
|
515
|
+
readonly entities?: readonly AnyEntity[] | undefined;
|
|
502
516
|
/** IDs or trail objects of downstream trails this trail may invoke via ctx.compose() */
|
|
503
517
|
readonly composes?: C;
|
|
504
518
|
/**
|
|
505
519
|
* Composition-only input schema — merged with `input` for `ctx.compose()` calls,
|
|
506
520
|
* invisible to public surfaces (CLI, MCP, HTTP).
|
|
507
521
|
*
|
|
508
|
-
* Fields here are available in the
|
|
522
|
+
* Fields here are available in the implementation but are not derived into CLI flags,
|
|
509
523
|
* MCP tool parameters, or HTTP request bodies. Use for data that only makes
|
|
510
524
|
* sense when one trail composes another (e.g. `forkedFrom`).
|
|
511
525
|
*/
|
|
@@ -564,8 +578,8 @@ export type TrailVisibility = 'public' | 'internal';
|
|
|
564
578
|
export interface Trail<I, O, CI = never> extends Omit<
|
|
565
579
|
TrailSpec<I, O, CI, readonly TrailRef[] | undefined>,
|
|
566
580
|
| 'args'
|
|
567
|
-
| '
|
|
568
|
-
| '
|
|
581
|
+
| 'implementation'
|
|
582
|
+
| 'entities'
|
|
569
583
|
| 'composes'
|
|
570
584
|
| 'composeInput'
|
|
571
585
|
| 'detours'
|
|
@@ -577,20 +591,20 @@ export interface Trail<I, O, CI = never> extends Omit<
|
|
|
577
591
|
> {
|
|
578
592
|
readonly kind: 'trail';
|
|
579
593
|
readonly id: string;
|
|
580
|
-
readonly
|
|
581
|
-
/**
|
|
582
|
-
readonly
|
|
594
|
+
readonly implementation: Implementation<ImplementationInput<I, CI>, O>;
|
|
595
|
+
/** Entities this trail operates on (always present, default []). */
|
|
596
|
+
readonly entities: readonly AnyEntity[];
|
|
583
597
|
/** IDs of downstream trails this trail may invoke via ctx.compose() (always present, default []) */
|
|
584
598
|
readonly composes: readonly string[];
|
|
585
599
|
/** Composition-only input schema, merged with `input` for ctx.compose() calls (optional) */
|
|
586
600
|
readonly composeInput?: z.ZodType<CI> | undefined;
|
|
587
|
-
/** Recovery paths activated when
|
|
601
|
+
/** Recovery paths activated when implementation fails with a matching error (always present, default []). */
|
|
588
602
|
readonly detours: readonly Detour<I, O, TrailsError>[];
|
|
589
603
|
/**
|
|
590
604
|
* Typed layers attached at trail scope (always present, default []).
|
|
591
605
|
*
|
|
592
|
-
* Composed innermost in the layer chain — closest to the
|
|
593
|
-
* composition order is `topo → surface → trail →
|
|
606
|
+
* Composed innermost in the layer chain — closest to the implementation. The final
|
|
607
|
+
* composition order is `topo → surface → trail → implementation` (outermost-first).
|
|
594
608
|
*/
|
|
595
609
|
readonly layers: readonly Layer[];
|
|
596
610
|
/** Resources this trail may access via resource.from(ctx) (always present, default []) */
|
|
@@ -616,18 +630,6 @@ export interface Trail<I, O, CI = never> extends Omit<
|
|
|
616
630
|
// Factory
|
|
617
631
|
// ---------------------------------------------------------------------------
|
|
618
632
|
|
|
619
|
-
/**
|
|
620
|
-
* Canonical scoped-signal id shape: `<scope>:<table>.<event>`.
|
|
621
|
-
*
|
|
622
|
-
* Matches exactly one `:` separating a non-empty scope from a non-empty
|
|
623
|
-
* dotted tail of at least two segments (e.g. `identity:users.created`).
|
|
624
|
-
* The scope forbids only `:` and whitespace so resource ids may contain
|
|
625
|
-
* dots for namespacing (e.g. `demo.store:gists.created`). Tail segments
|
|
626
|
-
* forbid both `:` and `.` so strings like `foo:bar` (no dot) or
|
|
627
|
-
* `a:b.c.d` stay unambiguous.
|
|
628
|
-
*/
|
|
629
|
-
const SCOPED_SIGNAL_ID = /^[^:\s]+:[^:.\s]+(?:\.[^:.\s]+)+$/;
|
|
630
|
-
|
|
631
633
|
const normalizeSignalRef = (entry: string | AnySignal): string => {
|
|
632
634
|
if (typeof entry === 'string') {
|
|
633
635
|
return entry;
|
|
@@ -638,19 +640,10 @@ const normalizeSignalRef = (entry: string | AnySignal): string => {
|
|
|
638
640
|
return entry.id;
|
|
639
641
|
}
|
|
640
642
|
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
|
|
645
|
-
// caller's explicit choice of scope would be lost and topo resolution
|
|
646
|
-
// would throw an ambiguity error.
|
|
647
|
-
//
|
|
648
|
-
// Use a strict predicate that matches the canonical scoped shape
|
|
649
|
-
// `<scope>:<table>.<event>` (exactly one `:` separating a non-empty scope
|
|
650
|
-
// from a non-empty dotted tail of at least two segments). A looser
|
|
651
|
-
// `includes(':')` check would let unscoped ids that happen to contain `:`
|
|
652
|
-
// slip past markerization and then fail to resolve at topo finalization.
|
|
653
|
-
if (SCOPED_SIGNAL_ID.test(entry.id)) {
|
|
643
|
+
// Bound refs preserve an explicit resource choice. Authored refs always
|
|
644
|
+
// become markers, even when a table name contains the scope separator.
|
|
645
|
+
// Ownership is a contract fact; do not infer it from signal ID grammar.
|
|
646
|
+
if (isBoundLateBoundSignal(entry)) {
|
|
654
647
|
return entry.id;
|
|
655
648
|
}
|
|
656
649
|
|
|
@@ -992,11 +985,11 @@ const normalizeVersionEntry = <CurrentInput, CurrentOutput>(
|
|
|
992
985
|
);
|
|
993
986
|
}
|
|
994
987
|
|
|
995
|
-
const
|
|
988
|
+
const hasImplementation = typeof raw['implementation'] === 'function';
|
|
996
989
|
const hasTranspose = raw['transpose'] !== undefined;
|
|
997
|
-
if (
|
|
990
|
+
if (hasImplementation && hasTranspose) {
|
|
998
991
|
throw new ValidationError(
|
|
999
|
-
`Trail "${trailId}" version ${version} cannot declare both
|
|
992
|
+
`Trail "${trailId}" version ${version} cannot declare both implementation and transpose`
|
|
1000
993
|
);
|
|
1001
994
|
}
|
|
1002
995
|
|
|
@@ -1013,11 +1006,14 @@ const normalizeVersionEntry = <CurrentInput, CurrentOutput>(
|
|
|
1013
1006
|
: { status: normalizeVersionStatus(trailId, version, raw['status']) }),
|
|
1014
1007
|
};
|
|
1015
1008
|
|
|
1016
|
-
if (
|
|
1009
|
+
if (hasImplementation) {
|
|
1017
1010
|
return Object.freeze({
|
|
1018
1011
|
...base,
|
|
1019
|
-
|
|
1020
|
-
await (raw['
|
|
1012
|
+
implementation: async (input: unknown, ctx: TrailContext) =>
|
|
1013
|
+
await (raw['implementation'] as Implementation<unknown, unknown>)(
|
|
1014
|
+
input,
|
|
1015
|
+
ctx
|
|
1016
|
+
),
|
|
1021
1017
|
...(raw['composeInput'] === undefined
|
|
1022
1018
|
? {}
|
|
1023
1019
|
: { composeInput: raw['composeInput'] }),
|
|
@@ -1153,7 +1149,7 @@ const normalizeCollections = <
|
|
|
1153
1149
|
): {
|
|
1154
1150
|
readonly args: readonly string[] | false | undefined;
|
|
1155
1151
|
readonly activationSources: readonly ActivationEntry[];
|
|
1156
|
-
readonly
|
|
1152
|
+
readonly entities: readonly AnyEntity[];
|
|
1157
1153
|
readonly detours: readonly Detour<I, O, TrailsError>[];
|
|
1158
1154
|
readonly fires: readonly string[];
|
|
1159
1155
|
readonly layers: readonly Layer[];
|
|
@@ -1164,8 +1160,8 @@ const normalizeCollections = <
|
|
|
1164
1160
|
return {
|
|
1165
1161
|
activationSources,
|
|
1166
1162
|
args: Array.isArray(spec.args) ? Object.freeze([...spec.args]) : spec.args,
|
|
1167
|
-
contours: Object.freeze([...(spec.contours ?? [])]),
|
|
1168
1163
|
detours: Object.freeze([...(spec.detours ?? [])]),
|
|
1164
|
+
entities: Object.freeze([...(spec.entities ?? [])]),
|
|
1169
1165
|
fires: Object.freeze((spec.fires ?? []).map(normalizeSignalRef)),
|
|
1170
1166
|
layers: Object.freeze([...(spec.layers ?? [])]),
|
|
1171
1167
|
on: extractSignalActivationIds(activationSources),
|
|
@@ -1184,14 +1180,14 @@ const normalizeCollections = <
|
|
|
1184
1180
|
* // ID as first argument (recommended for human authoring)
|
|
1185
1181
|
* const show = trail("entity.show", {
|
|
1186
1182
|
* input: z.object({ name: z.string() }),
|
|
1187
|
-
*
|
|
1183
|
+
* implementation: (input) => Result.ok(entity),
|
|
1188
1184
|
* });
|
|
1189
1185
|
*
|
|
1190
1186
|
* // Full spec object (for programmatic generation)
|
|
1191
1187
|
* const show = trail({
|
|
1192
1188
|
* id: "entity.show",
|
|
1193
1189
|
* input: z.object({ name: z.string() }),
|
|
1194
|
-
*
|
|
1190
|
+
* implementation: (input) => Result.ok(entity),
|
|
1195
1191
|
* });
|
|
1196
1192
|
* ```
|
|
1197
1193
|
*/
|
|
@@ -1281,21 +1277,28 @@ export function trail<
|
|
|
1281
1277
|
if (!resolved.spec) {
|
|
1282
1278
|
throw new TypeError('trail() requires a spec when an id is provided');
|
|
1283
1279
|
}
|
|
1284
|
-
|
|
1280
|
+
const rawSpec = resolved.spec as unknown as Record<string, unknown>;
|
|
1281
|
+
if (hasOwn(rawSpec, 'contours')) {
|
|
1282
|
+
throw new ValidationError(
|
|
1283
|
+
`Trail "${resolved.id}" uses retired "contours"; use "entities" instead`
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (hasOwn(rawSpec, 'marker')) {
|
|
1285
1288
|
throw new ValidationError(
|
|
1286
1289
|
`Trail "${resolved.id}" must not author marker; it is projected`
|
|
1287
1290
|
);
|
|
1288
1291
|
}
|
|
1289
1292
|
|
|
1290
1293
|
const {
|
|
1291
|
-
|
|
1294
|
+
implementation,
|
|
1292
1295
|
composeInput,
|
|
1293
1296
|
composes: rawComposes,
|
|
1294
1297
|
intent: rawIntent,
|
|
1295
1298
|
visibility: rawVisibility,
|
|
1296
1299
|
// Destructure away fields handled by normalizeCollections
|
|
1297
1300
|
args: _a,
|
|
1298
|
-
|
|
1301
|
+
entities: _c,
|
|
1299
1302
|
detours: _d,
|
|
1300
1303
|
fires: _f,
|
|
1301
1304
|
layers: _l,
|
|
@@ -1317,11 +1320,13 @@ export function trail<
|
|
|
1317
1320
|
return Object.freeze({
|
|
1318
1321
|
...spec,
|
|
1319
1322
|
...collections,
|
|
1320
|
-
blaze: async (input: BlazeInput<I, CI>, ctx: TrailContext) =>
|
|
1321
|
-
await blaze(input, ctx as ComposeContextFor<C>),
|
|
1322
1323
|
composeInput,
|
|
1323
1324
|
composes: Object.freeze((rawComposes ?? []).map(normalizeComposeRef)),
|
|
1324
1325
|
id: resolved.id,
|
|
1326
|
+
implementation: async (
|
|
1327
|
+
input: ImplementationInput<I, CI>,
|
|
1328
|
+
ctx: TrailContext
|
|
1329
|
+
) => await implementation(input, ctx as ComposeContextFor<C>),
|
|
1325
1330
|
intent: rawIntent ?? 'write',
|
|
1326
1331
|
kind: 'trail' as const,
|
|
1327
1332
|
...(rawVersion === undefined ? {} : { version: rawVersion }),
|
|
@@ -1331,14 +1336,14 @@ export function trail<
|
|
|
1331
1336
|
}
|
|
1332
1337
|
|
|
1333
1338
|
// Re-export types that callers of trail() will need
|
|
1334
|
-
// The Omit+override avoids a TypeScript limitation where
|
|
1339
|
+
// The Omit+override avoids a TypeScript limitation where ImplementationInput's conditional type
|
|
1335
1340
|
// makes Trail<any, any, any> structurally incompatible with Trail<I, O, never>.
|
|
1336
1341
|
/* oxlint-disable no-explicit-any -- existential type for heterogeneous collections */
|
|
1337
1342
|
export type AnyTrail = Omit<
|
|
1338
1343
|
Trail<any, any, never>,
|
|
1339
|
-
'
|
|
1344
|
+
'implementation' | 'composeInput'
|
|
1340
1345
|
> & {
|
|
1341
|
-
readonly
|
|
1346
|
+
readonly implementation: Implementation<any, any>;
|
|
1342
1347
|
readonly composeInput?: z.ZodType<any> | undefined;
|
|
1343
1348
|
};
|
|
1344
1349
|
/* oxlint-enable no-explicit-any */
|