@ontrails/core 1.0.0-beta.18 → 1.0.0-beta.19
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 +26 -0
- package/README.md +14 -27
- package/package.json +1 -1
- package/src/{cross-batch.ts → compose-batch.ts} +10 -10
- package/src/compose-schema.ts +36 -0
- package/src/contour.ts +2 -0
- package/src/draft.ts +2 -2
- package/src/errors.ts +65 -1
- package/src/execute.ts +304 -103
- package/src/fire.ts +2 -2
- package/src/index.ts +66 -10
- package/src/internal/fork-ctx.ts +4 -4
- package/src/layer-projection.ts +1 -0
- package/src/layer.ts +1 -1
- package/src/observe.ts +5 -5
- package/src/resource.ts +51 -32
- package/src/run.ts +24 -3
- package/src/schedule.ts +2 -0
- package/src/signal.ts +2 -0
- package/src/structured-examples.ts +8 -5
- package/src/surface-versioning.ts +42 -0
- package/src/topo.ts +3 -3
- package/src/trail.ts +673 -38
- package/src/type-utils.ts +21 -13
- package/src/types.ts +43 -21
- package/src/validate-established-topo.ts +3 -3
- package/src/validate-topo.ts +120 -30
- package/src/validation.ts +69 -10
- package/src/version-marker.ts +716 -0
- package/src/version-resolution.ts +308 -0
- package/src/version-runtime.ts +120 -0
- package/src/webhook.ts +2 -0
- package/src/cross-schema.ts +0 -36
package/src/fire.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* `ctx.fire()` resolves after dispatch is initiated and without a value.
|
|
24
24
|
* Unknown signals, invalid payloads, guard suppression, and consumer errors
|
|
25
25
|
* are logged/diagnosed but do NOT propagate back to the producer. Consumers
|
|
26
|
-
* that need transactional coupling should use `
|
|
26
|
+
* that need transactional coupling should use `composes:`.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import type { z } from 'zod';
|
|
@@ -617,7 +617,7 @@ const shouldInvokeConsumer = async (
|
|
|
617
617
|
*
|
|
618
618
|
* @remarks
|
|
619
619
|
* Signal delivery is fire-and-forget notification, not ordered orchestration;
|
|
620
|
-
* if one consumer depends on another, the dependency belongs in `
|
|
620
|
+
* if one consumer depends on another, the dependency belongs in `composes:`
|
|
621
621
|
* instead of sibling signal sequencing.
|
|
622
622
|
*
|
|
623
623
|
* `Promise.allSettled` preserves failure isolation for the background
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export {
|
|
|
9
9
|
AmbiguousError,
|
|
10
10
|
AssertionError,
|
|
11
11
|
NotFoundError,
|
|
12
|
+
VersionNotSupportedError,
|
|
12
13
|
AlreadyExistsError,
|
|
13
14
|
ConflictError,
|
|
14
15
|
PermissionError,
|
|
@@ -74,8 +75,9 @@ export type {
|
|
|
74
75
|
Implementation,
|
|
75
76
|
TrailContext,
|
|
76
77
|
TrailContextInit,
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
ComposeBatchOptions,
|
|
79
|
+
ComposeOptions,
|
|
80
|
+
ComposeFn,
|
|
79
81
|
FireFn,
|
|
80
82
|
LogLevel,
|
|
81
83
|
LogFormatter,
|
|
@@ -157,23 +159,77 @@ export {
|
|
|
157
159
|
projectActivationSourceDeclaration,
|
|
158
160
|
} from './activation-source-projection.js';
|
|
159
161
|
export type { ActivationSourceProjection } from './activation-source-projection.js';
|
|
160
|
-
export {
|
|
162
|
+
export {
|
|
163
|
+
deriveSupportedTrailVersions,
|
|
164
|
+
getTrailVersionEntryKind,
|
|
165
|
+
hasDeprecatedTrailVersionGuidance,
|
|
166
|
+
intentValues,
|
|
167
|
+
isArchivedTrailVersionEntry,
|
|
168
|
+
isDeprecatedTrailVersionEntry,
|
|
169
|
+
isLiveTrailVersionEntry,
|
|
170
|
+
trail,
|
|
171
|
+
} from './trail.js';
|
|
172
|
+
export {
|
|
173
|
+
TRAIL_VERSION_MARKER_LENGTH,
|
|
174
|
+
TRAIL_VERSION_MARKER_MIN_PREFIX_LENGTH,
|
|
175
|
+
assertTrailVersionMarker,
|
|
176
|
+
assertUniqueTrailVersionMarkers,
|
|
177
|
+
canonicalizeTrailVersionMarkerContent,
|
|
178
|
+
deriveCurrentTrailVersionMarker,
|
|
179
|
+
deriveCurrentTrailVersionMarkerContent,
|
|
180
|
+
deriveShortestUnambiguousTrailVersionMarkerPrefix,
|
|
181
|
+
deriveTrailVersionEntryMarker,
|
|
182
|
+
deriveTrailVersionEntryMarkerContent,
|
|
183
|
+
deriveTrailVersionMarker,
|
|
184
|
+
deriveTrailVersionMarkers,
|
|
185
|
+
normalizeTrailVersionMarkerPrefix,
|
|
186
|
+
resolveTrailVersionMarkerPrefix,
|
|
187
|
+
} from './version-marker.js';
|
|
188
|
+
export {
|
|
189
|
+
parseTrailIdVersionReference,
|
|
190
|
+
resolveTrailVersion,
|
|
191
|
+
} from './version-resolution.js';
|
|
161
192
|
export type {
|
|
162
193
|
AnyTrail,
|
|
163
194
|
BlazeInput,
|
|
164
195
|
Intent,
|
|
165
196
|
Trail,
|
|
197
|
+
TrailVersionEntry,
|
|
198
|
+
TrailVersionEntryKind,
|
|
199
|
+
TrailVersionArchivedStatus,
|
|
200
|
+
TrailVersionDeprecatedStatus,
|
|
201
|
+
TrailVersionForkEntry,
|
|
202
|
+
TrailVersionRevisionEntry,
|
|
203
|
+
TrailVersions,
|
|
204
|
+
TrailVersionStatus,
|
|
205
|
+
TrailVersionTranspose,
|
|
206
|
+
TrailVersionTransposeInput,
|
|
207
|
+
TrailVersionTransposeOutput,
|
|
166
208
|
TrailSpec,
|
|
167
209
|
TrailExample,
|
|
168
210
|
TrailExampleSignalAssertion,
|
|
169
211
|
TrailVisibility,
|
|
212
|
+
VersionContract,
|
|
213
|
+
VersionEntry,
|
|
170
214
|
} from './trail.js';
|
|
215
|
+
export type {
|
|
216
|
+
TrailVersionMarkerBinding,
|
|
217
|
+
TrailVersionMarkerRecord,
|
|
218
|
+
TrailVersionMarkerResolution,
|
|
219
|
+
} from './version-marker.js';
|
|
220
|
+
export type {
|
|
221
|
+
ParsedTrailVersionReference,
|
|
222
|
+
ResolvedTrailVersion,
|
|
223
|
+
TrailVersionReference,
|
|
224
|
+
} from './version-resolution.js';
|
|
171
225
|
export {
|
|
172
226
|
filterSurfaceTrails,
|
|
173
227
|
matchesTrailPattern,
|
|
174
228
|
shouldIncludeTrailForSurface,
|
|
175
229
|
} from './surface-filter.js';
|
|
176
230
|
export type { SurfaceFilterOptions } from './surface-filter.js';
|
|
231
|
+
export { deriveSurfaceTrailVersionProjections } from './surface-versioning.js';
|
|
232
|
+
export type { SurfaceTrailVersionProjection } from './surface-versioning.js';
|
|
177
233
|
export {
|
|
178
234
|
shouldValidateSurfaceTopo,
|
|
179
235
|
validateSurfaceTopo,
|
|
@@ -202,7 +258,7 @@ export type {
|
|
|
202
258
|
|
|
203
259
|
// Type utilities
|
|
204
260
|
export type {
|
|
205
|
-
|
|
261
|
+
ComposeInput,
|
|
206
262
|
TrailInput,
|
|
207
263
|
TrailOutput,
|
|
208
264
|
TrailResult,
|
|
@@ -388,13 +444,13 @@ export type {
|
|
|
388
444
|
export { deriveCliPath, deriveFields } from './derive.js';
|
|
389
445
|
export type { Field, FieldOverride } from './derive.js';
|
|
390
446
|
|
|
391
|
-
//
|
|
392
|
-
export {
|
|
447
|
+
// Compose schema
|
|
448
|
+
export { buildComposeValidationSchema } from './compose-schema.js';
|
|
393
449
|
export {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
} from './
|
|
450
|
+
claimNextComposeBatchIndex,
|
|
451
|
+
createComposeBatchValidationResults,
|
|
452
|
+
normalizeComposeBatchConcurrency,
|
|
453
|
+
} from './compose-batch.js';
|
|
398
454
|
|
|
399
455
|
// Execute
|
|
400
456
|
export { DETOUR_MAX_ATTEMPTS_CAP } from './detours.js';
|
package/src/internal/fork-ctx.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Internal helper for "forking" a trail context.
|
|
3
3
|
*
|
|
4
4
|
* Several execution sites need to derive a child context from a parent
|
|
5
|
-
* context while **resetting** a well-known set of bound closures (`
|
|
5
|
+
* context while **resetting** a well-known set of bound closures (`compose`,
|
|
6
6
|
* `fire`, `resource`). Those closures capture the parent scope, so reusing
|
|
7
7
|
* them on the child would re-enter execution with the wrong attribution,
|
|
8
8
|
* the wrong resource scope, or the wrong fan-out identity. The reset list
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import type { TrailContext, TrailContextInit } from '../types.js';
|
|
25
25
|
|
|
26
26
|
/** Keys cleared by default when forking a context. */
|
|
27
|
-
export type ForkCtxResetKey = '
|
|
27
|
+
export type ForkCtxResetKey = 'compose' | 'fire' | 'resource';
|
|
28
28
|
|
|
29
29
|
/** Override fields callers are allowed to apply when forking a context. */
|
|
30
30
|
export type ForkCtxOverrides = Readonly<
|
|
@@ -32,7 +32,7 @@ export type ForkCtxOverrides = Readonly<
|
|
|
32
32
|
>;
|
|
33
33
|
|
|
34
34
|
const DEFAULT_RESET_KEYS: readonly ForkCtxResetKey[] = [
|
|
35
|
-
'
|
|
35
|
+
'compose',
|
|
36
36
|
'fire',
|
|
37
37
|
'resource',
|
|
38
38
|
];
|
|
@@ -53,7 +53,7 @@ export const forkCtx = <
|
|
|
53
53
|
TCtx extends Partial<
|
|
54
54
|
Pick<
|
|
55
55
|
TrailContextInit,
|
|
56
|
-
'
|
|
56
|
+
'compose' | 'env' | 'extensions' | 'fire' | 'logger' | 'resource'
|
|
57
57
|
>
|
|
58
58
|
>,
|
|
59
59
|
>(
|
package/src/layer-projection.ts
CHANGED
package/src/layer.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { Implementation } from './types.js';
|
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* A composable, named layer that wraps
|
|
11
|
+
* A composable, named layer that wraps blazes.
|
|
12
12
|
*
|
|
13
13
|
* Layers attach at trail, surface, or topo scope and may declare an object
|
|
14
14
|
* `input` schema describing the configuration they need from the surrounding
|
package/src/observe.ts
CHANGED
|
@@ -128,7 +128,7 @@ export const isObserveInput = (
|
|
|
128
128
|
}
|
|
129
129
|
if (isTraceSink(value) && !isLogSink(value)) {
|
|
130
130
|
// A bare TraceSink (no `name`) is unambiguous — `normalizeObserve`
|
|
131
|
-
//
|
|
131
|
+
// accepts it via the `isTraceSink` fallthrough. A LogSink shape is
|
|
132
132
|
// ambiguous (matches both guards) and would be rejected by
|
|
133
133
|
// `normalizeObserve`, so the guard rejects it here too.
|
|
134
134
|
return true;
|
|
@@ -148,7 +148,7 @@ export const hasObserveCapabilities = (value: unknown): boolean =>
|
|
|
148
148
|
/**
|
|
149
149
|
* Returns true when `value` is shaped like the explicit `{ log?, trace? }`
|
|
150
150
|
* `ObserveConfig` payload. Exposed for the topo classifier so a config-style
|
|
151
|
-
* trailing argument is unambiguously
|
|
151
|
+
* trailing argument is unambiguously classified as options.
|
|
152
152
|
*/
|
|
153
153
|
export const isObserveConfig = (value: unknown): value is ObserveConfig =>
|
|
154
154
|
isObserveConfigShape(value);
|
|
@@ -212,15 +212,15 @@ const stringifyDefaultConsoleRecord = (record: LogRecord): string => {
|
|
|
212
212
|
/**
|
|
213
213
|
* In-core mirror of `@ontrails/observe`'s `createConsoleSink` shape, kept
|
|
214
214
|
* minimal and private to avoid a reverse dependency from `@ontrails/core`
|
|
215
|
-
* onto `@ontrails/observe`. It mirrors the console level
|
|
215
|
+
* onto `@ontrails/observe`. It mirrors the console level mapping in
|
|
216
216
|
* `packages/observe/src/sinks.ts:50` and emits each record as a single-line
|
|
217
|
-
* JSON object
|
|
217
|
+
* JSON object written to the matching `console.{debug|info|warn|error}` method.
|
|
218
218
|
*
|
|
219
219
|
* @remarks
|
|
220
220
|
* Used as the default `observe.log` target when `topo()` is called without an
|
|
221
221
|
* explicit `observe` option, so every app gets a non-null `ctx.logger` with
|
|
222
222
|
* structured stdout output and zero configuration. Apps that want richer
|
|
223
|
-
* formatting, file destinations, or custom
|
|
223
|
+
* formatting, file destinations, or custom sink behavior should pass an explicit
|
|
224
224
|
* `observe` option, which fully replaces this default.
|
|
225
225
|
*/
|
|
226
226
|
const createDefaultConsoleSink = (): LogSink => ({
|
package/src/resource.ts
CHANGED
|
@@ -54,10 +54,19 @@ export interface ResourceSpec<T, C = unknown> {
|
|
|
54
54
|
readonly meta?: Readonly<Record<string, unknown>> | undefined;
|
|
55
55
|
/** Signals projected or owned by this resource. */
|
|
56
56
|
readonly signals?: readonly AnySignal[] | undefined;
|
|
57
|
+
/** Reserved for future resource-specific design; trail versioning is trail-only. */
|
|
58
|
+
readonly version?: never;
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
type ResourceSpecWithConfig<T, S extends z.ZodTypeAny> = Omit<
|
|
62
|
+
ResourceSpec<T, z.infer<S>>,
|
|
63
|
+
'config'
|
|
64
|
+
> & {
|
|
65
|
+
readonly config: S;
|
|
66
|
+
};
|
|
67
|
+
|
|
59
68
|
/** A typed resource definition. */
|
|
60
|
-
export interface Resource<T> extends ResourceSpec<T> {
|
|
69
|
+
export interface Resource<T, C = unknown> extends ResourceSpec<T, C> {
|
|
61
70
|
readonly kind: 'resource';
|
|
62
71
|
readonly id: string;
|
|
63
72
|
/** Read the resolved resource instance from a trail context. */
|
|
@@ -72,7 +81,7 @@ export interface Resource<T> extends ResourceSpec<T> {
|
|
|
72
81
|
* existential here.
|
|
73
82
|
*/
|
|
74
83
|
// oxlint-disable-next-line no-explicit-any -- existential type for heterogeneous resource collections
|
|
75
|
-
export type AnyResource = Resource<any>;
|
|
84
|
+
export type AnyResource = Resource<any, any>;
|
|
76
85
|
|
|
77
86
|
/** Explicit runtime overrides keyed by resource ID. */
|
|
78
87
|
export type ResourceOverrideMap = Readonly<Record<string, unknown>>;
|
|
@@ -114,37 +123,47 @@ export const createResourceLookup = (
|
|
|
114
123
|
* The resource object is inert until a later execution branch resolves concrete
|
|
115
124
|
* instances into TrailContext extensions.
|
|
116
125
|
*/
|
|
117
|
-
export
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
126
|
+
export function resource<T, S extends z.ZodTypeAny>(
|
|
127
|
+
id: string,
|
|
128
|
+
spec: ResourceSpecWithConfig<T, S>
|
|
129
|
+
): Resource<T, z.infer<S>>;
|
|
130
|
+
export function resource<T, C = unknown>(
|
|
131
|
+
id: string,
|
|
132
|
+
spec: ResourceSpec<T, C>
|
|
133
|
+
): Resource<T, C>;
|
|
134
|
+
export function resource<T, C = unknown>(
|
|
135
|
+
id: string,
|
|
136
|
+
spec: ResourceSpec<T, C>
|
|
137
|
+
): Resource<T, C> {
|
|
138
|
+
if (id.includes(':')) {
|
|
139
|
+
throw new InternalError(
|
|
140
|
+
`Resource "${id}" is invalid because resource ids may not contain ":"`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (spec.mock !== undefined && spec.unmockable !== undefined) {
|
|
144
|
+
throw new InternalError(
|
|
145
|
+
`Resource "${id}" cannot define both mock and unmockable`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (
|
|
149
|
+
spec.unmockable !== undefined &&
|
|
150
|
+
spec.unmockable.reason.trim().length === 0
|
|
151
|
+
) {
|
|
152
|
+
throw new InternalError(
|
|
153
|
+
`Resource "${id}" is invalid because unmockable.reason must not be empty`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
137
156
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
return Object.freeze({
|
|
158
|
+
...spec,
|
|
159
|
+
from(ctx: TrailContext): T {
|
|
160
|
+
const lookup = ctx.resource ?? createResourceLookup(() => ctx);
|
|
161
|
+
return lookup(this);
|
|
162
|
+
},
|
|
163
|
+
id,
|
|
164
|
+
kind: 'resource' as const,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
148
167
|
|
|
149
168
|
/** Narrow unknown values to resource definitions during topo discovery. */
|
|
150
169
|
export const isResource = (value: unknown): value is AnyResource => {
|
package/src/run.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import type { Topo } from './topo.js';
|
|
9
9
|
import { executeTrail } from './execute.js';
|
|
10
10
|
import type { ExecuteTrailOptions } from './execute.js';
|
|
11
|
-
import {
|
|
11
|
+
import { parseTrailIdVersionReference } from './version-resolution.js';
|
|
12
|
+
import { NotFoundError, ValidationError } from './errors.js';
|
|
12
13
|
import { Result } from './result.js';
|
|
13
14
|
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
@@ -43,7 +44,21 @@ export const run = (
|
|
|
43
44
|
input: unknown,
|
|
44
45
|
options?: RunOptions
|
|
45
46
|
): Promise<Result<unknown, Error>> => {
|
|
46
|
-
const
|
|
47
|
+
const parsed = parseTrailIdVersionReference(id);
|
|
48
|
+
if (parsed.isErr()) {
|
|
49
|
+
return Promise.resolve(Result.err(parsed.error));
|
|
50
|
+
}
|
|
51
|
+
if (parsed.value.version !== undefined && options?.version !== undefined) {
|
|
52
|
+
return Promise.resolve(
|
|
53
|
+
Result.err(
|
|
54
|
+
new ValidationError(
|
|
55
|
+
`Trail "${parsed.value.id}" cannot combine an @version suffix with options.version`
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const trail = topo.get(parsed.value.id);
|
|
47
62
|
if (trail === undefined) {
|
|
48
63
|
return Promise.resolve(
|
|
49
64
|
Result.err(
|
|
@@ -51,5 +66,11 @@ export const run = (
|
|
|
51
66
|
)
|
|
52
67
|
);
|
|
53
68
|
}
|
|
54
|
-
return executeTrail(trail, input, {
|
|
69
|
+
return executeTrail(trail, input, {
|
|
70
|
+
...options,
|
|
71
|
+
...(parsed.value.version === undefined
|
|
72
|
+
? {}
|
|
73
|
+
: { version: parsed.value.version }),
|
|
74
|
+
topo,
|
|
75
|
+
});
|
|
55
76
|
};
|
package/src/schedule.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface ScheduleSpec<TInput = unknown> {
|
|
|
11
11
|
readonly input?: TInput | undefined;
|
|
12
12
|
readonly meta?: ActivationSourceMeta | undefined;
|
|
13
13
|
readonly timezone?: string | undefined;
|
|
14
|
+
/** Reserved for future schedule-specific design; trail versioning is trail-only. */
|
|
15
|
+
readonly version?: never;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface ScheduleSource<TInput = unknown> extends ActivationSource {
|
package/src/signal.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface SignalSpec<T> {
|
|
|
39
39
|
readonly meta?: Readonly<Record<string, unknown>> | undefined;
|
|
40
40
|
/** Trail IDs that produce this signal (e.g. the trails it originates from). */
|
|
41
41
|
readonly from?: readonly string[] | undefined;
|
|
42
|
+
/** Reserved for future signal-specific design; trail versioning is trail-only. */
|
|
43
|
+
readonly version?: never;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
// ---------------------------------------------------------------------------
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TrailExample, TrailExampleSignalAssertion } from './trail.js';
|
|
2
2
|
|
|
3
3
|
export interface StructuredTrailExampleProvenance {
|
|
4
|
-
readonly source: 'trail.examples';
|
|
4
|
+
readonly source: 'trail.examples' | 'trail.versions.examples';
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export interface StructuredSignalExampleProvenance {
|
|
@@ -152,7 +152,8 @@ const projectSignalAssertions = (
|
|
|
152
152
|
};
|
|
153
153
|
|
|
154
154
|
const projectExample = (
|
|
155
|
-
example: TrailExample<unknown, unknown
|
|
155
|
+
example: TrailExample<unknown, unknown>,
|
|
156
|
+
provenance: StructuredTrailExampleProvenance
|
|
156
157
|
): StructuredTrailExample | undefined => {
|
|
157
158
|
const input = toJsonSerializable(example.input);
|
|
158
159
|
if (input === undefined) {
|
|
@@ -163,7 +164,7 @@ const projectExample = (
|
|
|
163
164
|
input,
|
|
164
165
|
kind: example.error === undefined ? 'success' : 'error',
|
|
165
166
|
name: example.name,
|
|
166
|
-
provenance
|
|
167
|
+
provenance,
|
|
167
168
|
};
|
|
168
169
|
|
|
169
170
|
if (example.description !== undefined) {
|
|
@@ -213,14 +214,16 @@ const projectSignalExample = (
|
|
|
213
214
|
};
|
|
214
215
|
|
|
215
216
|
export const deriveStructuredTrailExamples = (
|
|
216
|
-
examples: readonly TrailExample<unknown, unknown>[] | undefined
|
|
217
|
+
examples: readonly TrailExample<unknown, unknown>[] | undefined,
|
|
218
|
+
options?: { readonly provenance?: StructuredTrailExampleProvenance }
|
|
217
219
|
): readonly StructuredTrailExample[] | undefined => {
|
|
218
220
|
if (examples === undefined || examples.length === 0) {
|
|
219
221
|
return undefined;
|
|
220
222
|
}
|
|
221
223
|
|
|
224
|
+
const provenance = options?.provenance ?? { source: 'trail.examples' };
|
|
222
225
|
const projected = examples
|
|
223
|
-
.map(projectExample)
|
|
226
|
+
.map((example) => projectExample(example, provenance))
|
|
224
227
|
.filter(
|
|
225
228
|
(example): example is StructuredTrailExample => example !== undefined
|
|
226
229
|
);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AnyTrail, TrailVersionStatus } from './trail.js';
|
|
2
|
+
import {
|
|
3
|
+
deriveSupportedTrailVersions,
|
|
4
|
+
isDeprecatedTrailVersionEntry,
|
|
5
|
+
} from './trail.js';
|
|
6
|
+
import { deriveTrailVersionMarkers } from './version-marker.js';
|
|
7
|
+
|
|
8
|
+
export interface SurfaceTrailVersionProjection {
|
|
9
|
+
readonly current: boolean;
|
|
10
|
+
readonly deprecated: boolean;
|
|
11
|
+
readonly marker?: string | undefined;
|
|
12
|
+
readonly status?: TrailVersionStatus | undefined;
|
|
13
|
+
readonly version: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const deriveSurfaceTrailVersionProjections = (
|
|
17
|
+
trail: AnyTrail
|
|
18
|
+
): readonly SurfaceTrailVersionProjection[] | undefined => {
|
|
19
|
+
if (trail.version === undefined) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const markers = new Map(
|
|
24
|
+
deriveTrailVersionMarkers(trail).map((record) => [
|
|
25
|
+
record.version,
|
|
26
|
+
record.marker,
|
|
27
|
+
])
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return deriveSupportedTrailVersions(trail).map((version) => {
|
|
31
|
+
const entry = trail.versions?.[version];
|
|
32
|
+
const marker = markers.get(version);
|
|
33
|
+
return {
|
|
34
|
+
current: version === trail.version,
|
|
35
|
+
deprecated:
|
|
36
|
+
entry === undefined ? false : isDeprecatedTrailVersionEntry(entry),
|
|
37
|
+
...(marker === undefined ? {} : { marker }),
|
|
38
|
+
...(entry?.status === undefined ? {} : { status: entry.status }),
|
|
39
|
+
version,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
};
|
package/src/topo.ts
CHANGED
|
@@ -578,7 +578,7 @@ const disambiguateBrandedObserve = (options: TopoOptions): TopoOptions => {
|
|
|
578
578
|
* 1. Branded via `topo.options()` → always options. Unknown option
|
|
579
579
|
* keys throw, and downstream `normalizeObserve` rejects malformed
|
|
580
580
|
* values. A bare `LogSink` (`{ name, write }`) in the `observe`
|
|
581
|
-
* slot is auto-
|
|
581
|
+
* slot is auto-wrapped as `{ log: sink }` so the brand is the
|
|
582
582
|
* complete escape hatch the docs promise.
|
|
583
583
|
* 2. The shape does not look like `TopoOptions` (mixed keys or no
|
|
584
584
|
* keys) → module.
|
|
@@ -621,11 +621,11 @@ const classifyTrailingArgument = (
|
|
|
621
621
|
};
|
|
622
622
|
}
|
|
623
623
|
// Branding via `topo.options()` is the documented escape hatch for
|
|
624
|
-
// disambiguating bare-sink shorthand.
|
|
624
|
+
// disambiguating bare-sink shorthand. Wrap a bare `LogSink` in the
|
|
625
625
|
// explicit `{ log: sink }` slot before handing off to `normalizeObserve`,
|
|
626
626
|
// which would otherwise reject it as ambiguous (a LogSink shape matches
|
|
627
627
|
// both `isLogSink` and `isTraceSink`). A bare TraceSink (no `name`) does
|
|
628
|
-
// not need rewriting because `normalizeObserve` already
|
|
628
|
+
// not need rewriting because `normalizeObserve` already accepts it via
|
|
629
629
|
// the `isTraceSink` fallthrough.
|
|
630
630
|
return {
|
|
631
631
|
kind: 'options',
|