@ontrails/core 0.2.0

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +849 -0
  2. package/README.md +190 -0
  3. package/package.json +36 -0
  4. package/src/activation-provenance.ts +116 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-derivation.ts +227 -0
  7. package/src/activation-source.ts +93 -0
  8. package/src/blob-ref.ts +90 -0
  9. package/src/branded.ts +135 -0
  10. package/src/collections.ts +99 -0
  11. package/src/compose-batch.ts +69 -0
  12. package/src/compose-schema.ts +36 -0
  13. package/src/context.ts +66 -0
  14. package/src/derive.ts +485 -0
  15. package/src/detours.ts +8 -0
  16. package/src/diagnostics.ts +21 -0
  17. package/src/draft.ts +350 -0
  18. package/src/entity.ts +346 -0
  19. package/src/error-rendering.ts +87 -0
  20. package/src/errors.ts +483 -0
  21. package/src/execute.ts +1577 -0
  22. package/src/fetch.ts +138 -0
  23. package/src/fire.ts +1172 -0
  24. package/src/glob.ts +81 -0
  25. package/src/guards.ts +37 -0
  26. package/src/index.ts +704 -0
  27. package/src/internal/fork-ctx.ts +69 -0
  28. package/src/layer-field-rendering.ts +193 -0
  29. package/src/layer.ts +81 -0
  30. package/src/observe.ts +361 -0
  31. package/src/path-scope.ts +66 -0
  32. package/src/path-security.ts +98 -0
  33. package/src/patterns/bulk.ts +16 -0
  34. package/src/patterns/change.ts +12 -0
  35. package/src/patterns/date-range.ts +12 -0
  36. package/src/patterns/index.ts +8 -0
  37. package/src/patterns/pagination.ts +22 -0
  38. package/src/patterns/progress.ts +13 -0
  39. package/src/patterns/sorting.ts +14 -0
  40. package/src/patterns/status.ts +11 -0
  41. package/src/patterns/timestamps.ts +12 -0
  42. package/src/permits.ts +12 -0
  43. package/src/queue.ts +163 -0
  44. package/src/redaction/index.ts +3 -0
  45. package/src/redaction/patterns.ts +50 -0
  46. package/src/redaction/redactor.ts +178 -0
  47. package/src/resilience.ts +234 -0
  48. package/src/resource-config.ts +804 -0
  49. package/src/resource.ts +194 -0
  50. package/src/result.ts +212 -0
  51. package/src/run.ts +76 -0
  52. package/src/runtime-builtins.ts +69 -0
  53. package/src/schedule-runtime.ts +689 -0
  54. package/src/schedule.ts +326 -0
  55. package/src/serialization.ts +265 -0
  56. package/src/sha256.ts +136 -0
  57. package/src/signal-diagnostics.ts +633 -0
  58. package/src/signal-ref.ts +111 -0
  59. package/src/signal.ts +104 -0
  60. package/src/store/accessor-protocol.ts +56 -0
  61. package/src/store/index.ts +4 -0
  62. package/src/structured-examples.ts +248 -0
  63. package/src/surface-derivation.ts +91 -0
  64. package/src/surface-filter.ts +101 -0
  65. package/src/surface-overlay.ts +694 -0
  66. package/src/surface-versioning.ts +42 -0
  67. package/src/topo.ts +835 -0
  68. package/src/tracing.ts +346 -0
  69. package/src/trail-id-glob.ts +15 -0
  70. package/src/trail.ts +1351 -0
  71. package/src/trails/derive-trail.ts +835 -0
  72. package/src/trails/index.ts +9 -0
  73. package/src/trails/ingest.ts +152 -0
  74. package/src/trails-db.ts +212 -0
  75. package/src/transport-error-map.ts +163 -0
  76. package/src/type-utils.ts +87 -0
  77. package/src/types.ts +300 -0
  78. package/src/validate-established-topo.ts +73 -0
  79. package/src/validate-topo.ts +725 -0
  80. package/src/validation.ts +330 -0
  81. package/src/version-marker.ts +716 -0
  82. package/src/version-resolution.ts +308 -0
  83. package/src/version-runtime.ts +120 -0
  84. package/src/webhook.ts +461 -0
  85. package/src/workspace.ts +244 -0
  86. package/src/zod-wrappers.ts +72 -0
@@ -0,0 +1,308 @@
1
+ import {
2
+ AmbiguousError,
3
+ TrailsError,
4
+ ValidationError,
5
+ VersionNotSupportedError,
6
+ } from './errors.js';
7
+ import { Result } from './result.js';
8
+ import {
9
+ deriveSupportedTrailVersions,
10
+ getTrailVersionEntryKind,
11
+ isArchivedTrailVersionEntry,
12
+ } from './trail.js';
13
+ import type {
14
+ AnyTrail,
15
+ TrailVersionForkEntry,
16
+ TrailVersionRevisionEntry,
17
+ } from './trail.js';
18
+ import {
19
+ TRAIL_VERSION_MARKER_LENGTH,
20
+ TRAIL_VERSION_MARKER_MIN_PREFIX_LENGTH,
21
+ deriveTrailVersionMarkers,
22
+ } from './version-marker.js';
23
+
24
+ export type TrailVersionReference = number | string;
25
+
26
+ export interface ParsedTrailVersionReference {
27
+ readonly id: string;
28
+ readonly version?: TrailVersionReference | undefined;
29
+ }
30
+
31
+ export type ResolvedTrailVersion =
32
+ | {
33
+ readonly current: true;
34
+ readonly kind: 'current';
35
+ readonly marker?: string | undefined;
36
+ readonly version?: number | undefined;
37
+ }
38
+ | {
39
+ readonly current: false;
40
+ readonly deprecated: boolean;
41
+ readonly entry: TrailVersionForkEntry;
42
+ readonly kind: 'fork';
43
+ readonly marker?: string | undefined;
44
+ readonly version: number;
45
+ }
46
+ | {
47
+ readonly current: false;
48
+ readonly deprecated: boolean;
49
+ readonly entry: TrailVersionRevisionEntry;
50
+ readonly kind: 'revision';
51
+ readonly marker?: string | undefined;
52
+ readonly version: number;
53
+ };
54
+
55
+ interface ParsedVersionReference {
56
+ readonly display: number | string;
57
+ readonly markerPrefix?: string | undefined;
58
+ readonly number?: number | undefined;
59
+ }
60
+
61
+ type MarkerResolvableTrail = Pick<
62
+ AnyTrail,
63
+ | 'composes'
64
+ | 'detours'
65
+ | 'id'
66
+ | 'input'
67
+ | 'output'
68
+ | 'resources'
69
+ | 'version'
70
+ | 'versions'
71
+ >;
72
+
73
+ const versionRefPattern = /^(.+)@(.+)$/;
74
+ const positiveIntegerPattern = /^[1-9]\d*$/;
75
+ const markerPrefixPattern = /^[0-9a-fA-F]+$/;
76
+
77
+ const parsePositiveInteger = (value: string): number | undefined => {
78
+ if (!positiveIntegerPattern.test(value)) {
79
+ return undefined;
80
+ }
81
+ const version = Number(value);
82
+ return Number.isSafeInteger(version) ? version : undefined;
83
+ };
84
+
85
+ const isInlineVersionReference = (value: string): boolean =>
86
+ parsePositiveInteger(value) !== undefined ||
87
+ (value.length >= TRAIL_VERSION_MARKER_MIN_PREFIX_LENGTH &&
88
+ value.length <= TRAIL_VERSION_MARKER_LENGTH &&
89
+ markerPrefixPattern.test(value));
90
+
91
+ const unsupportedVersion = (
92
+ trail: Pick<AnyTrail, 'id' | 'version' | 'versions'>,
93
+ requested: number | string,
94
+ reason: string
95
+ ): VersionNotSupportedError => {
96
+ const supported = deriveSupportedTrailVersions(trail);
97
+ return new VersionNotSupportedError(trail.id, requested, supported, reason);
98
+ };
99
+
100
+ export const parseTrailIdVersionReference = (
101
+ id: string
102
+ ): Result<ParsedTrailVersionReference, ValidationError> => {
103
+ const match = versionRefPattern.exec(id);
104
+ if (match === null) {
105
+ return Result.ok({ id });
106
+ }
107
+
108
+ const [, baseId, rawVersion] = match;
109
+ if (baseId === undefined || rawVersion === undefined) {
110
+ return Result.err(
111
+ new ValidationError(
112
+ 'Trail version reference must use trail.id@N or trail.id@<marker-prefix>'
113
+ )
114
+ );
115
+ }
116
+ if (!isInlineVersionReference(rawVersion)) {
117
+ return Result.ok({ id });
118
+ }
119
+
120
+ return Result.ok({ id: baseId, version: rawVersion });
121
+ };
122
+
123
+ const parseVersionReference = (
124
+ reference: TrailVersionReference
125
+ ): Result<ParsedVersionReference, Error> => {
126
+ if (typeof reference === 'number') {
127
+ return Number.isSafeInteger(reference) && reference > 0
128
+ ? Result.ok({ display: reference, number: reference })
129
+ : Result.err(
130
+ new ValidationError(
131
+ 'Trail version reference must be a positive integer'
132
+ )
133
+ );
134
+ }
135
+
136
+ const markerRequested = reference.startsWith('@');
137
+ const raw = markerRequested ? reference.slice(1) : reference;
138
+ if (raw.length === 0) {
139
+ return Result.err(
140
+ new ValidationError('Trail version reference must not be empty')
141
+ );
142
+ }
143
+
144
+ const number = parsePositiveInteger(raw);
145
+ if (!markerRequested && number !== undefined) {
146
+ return Result.ok({
147
+ display: reference,
148
+ markerPrefix:
149
+ raw.length >= TRAIL_VERSION_MARKER_MIN_PREFIX_LENGTH &&
150
+ raw.length <= TRAIL_VERSION_MARKER_LENGTH &&
151
+ markerPrefixPattern.test(raw)
152
+ ? raw.toLowerCase()
153
+ : undefined,
154
+ number,
155
+ });
156
+ }
157
+
158
+ if (
159
+ raw.length >= TRAIL_VERSION_MARKER_MIN_PREFIX_LENGTH &&
160
+ raw.length <= TRAIL_VERSION_MARKER_LENGTH &&
161
+ markerPrefixPattern.test(raw)
162
+ ) {
163
+ return Result.ok({
164
+ display: reference,
165
+ markerPrefix: raw.toLowerCase(),
166
+ });
167
+ }
168
+
169
+ return Result.err(
170
+ new ValidationError(
171
+ 'Trail version reference must be a positive integer or marker prefix'
172
+ )
173
+ );
174
+ };
175
+
176
+ const trailHasVersion = (
177
+ trail: Pick<AnyTrail, 'version' | 'versions'>,
178
+ version: number
179
+ ): boolean =>
180
+ trail.version === version || trail.versions?.[version] !== undefined;
181
+
182
+ const resolveMarkerReference = (
183
+ trail: MarkerResolvableTrail,
184
+ requested: number | string,
185
+ prefix: string
186
+ ): Result<{ readonly marker: string; readonly version: number }, Error> => {
187
+ let records: ReturnType<typeof deriveTrailVersionMarkers>;
188
+ try {
189
+ records = deriveTrailVersionMarkers(trail);
190
+ } catch (error: unknown) {
191
+ return Result.err(
192
+ error instanceof TrailsError
193
+ ? error
194
+ : new ValidationError(
195
+ `Trail version marker derivation failed: ${String(error)}`
196
+ )
197
+ );
198
+ }
199
+ const matches = records.filter((record) => record.marker.startsWith(prefix));
200
+
201
+ if (matches.length === 0) {
202
+ return Result.err(unsupportedVersion(trail, requested, 'marker missing'));
203
+ }
204
+ if (matches.length > 1) {
205
+ return Result.err(
206
+ new AmbiguousError(
207
+ `Trail version marker prefix ${prefix} is ambiguous across versions ${matches.map((record) => record.version).join(', ')}`
208
+ )
209
+ );
210
+ }
211
+
212
+ const match = matches[0] as (typeof matches)[number];
213
+
214
+ return Result.ok({ marker: match.marker, version: match.version });
215
+ };
216
+
217
+ const resolveRequestedVersionNumber = (
218
+ trail: MarkerResolvableTrail,
219
+ reference: TrailVersionReference
220
+ ): Result<
221
+ { readonly marker?: string | undefined; readonly version: number },
222
+ Error
223
+ > => {
224
+ const parsed = parseVersionReference(reference);
225
+ if (parsed.isErr()) {
226
+ return parsed;
227
+ }
228
+
229
+ const { display, markerPrefix, number } = parsed.value;
230
+ if (number !== undefined && trailHasVersion(trail, number)) {
231
+ return Result.ok({ version: number });
232
+ }
233
+
234
+ if (markerPrefix !== undefined) {
235
+ const marker = resolveMarkerReference(trail, display, markerPrefix);
236
+ if (marker.isOk()) {
237
+ return marker;
238
+ }
239
+ if (number === undefined) {
240
+ return marker;
241
+ }
242
+ }
243
+
244
+ return Result.err(unsupportedVersion(trail, display, 'missing'));
245
+ };
246
+
247
+ export const resolveTrailVersion = (
248
+ trail: MarkerResolvableTrail,
249
+ reference?: TrailVersionReference | undefined
250
+ ): Result<ResolvedTrailVersion, Error> => {
251
+ if (reference === undefined) {
252
+ return Result.ok({
253
+ current: true,
254
+ kind: 'current',
255
+ ...(trail.version === undefined ? {} : { version: trail.version }),
256
+ });
257
+ }
258
+
259
+ if (trail.version === undefined) {
260
+ return Result.err(unsupportedVersion(trail, reference, 'unversioned'));
261
+ }
262
+
263
+ const requested = resolveRequestedVersionNumber(trail, reference);
264
+ if (requested.isErr()) {
265
+ return requested;
266
+ }
267
+
268
+ if (requested.value.version === trail.version) {
269
+ return Result.ok({
270
+ current: true,
271
+ kind: 'current',
272
+ ...(requested.value.marker === undefined
273
+ ? {}
274
+ : { marker: requested.value.marker }),
275
+ version: trail.version,
276
+ });
277
+ }
278
+
279
+ const entry = trail.versions?.[requested.value.version];
280
+ if (entry === undefined) {
281
+ return Result.err(unsupportedVersion(trail, reference, 'missing'));
282
+ }
283
+ if (isArchivedTrailVersionEntry(entry)) {
284
+ return Result.err(unsupportedVersion(trail, reference, 'archived'));
285
+ }
286
+
287
+ const kind = getTrailVersionEntryKind(entry);
288
+ const base = {
289
+ current: false,
290
+ deprecated: entry.status?.state === 'deprecated',
291
+ ...(requested.value.marker === undefined
292
+ ? {}
293
+ : { marker: requested.value.marker }),
294
+ version: requested.value.version,
295
+ } as const;
296
+
297
+ return kind === 'fork'
298
+ ? Result.ok({
299
+ ...base,
300
+ entry: entry as TrailVersionForkEntry,
301
+ kind,
302
+ })
303
+ : Result.ok({
304
+ ...base,
305
+ entry: entry as TrailVersionRevisionEntry,
306
+ kind,
307
+ });
308
+ };
@@ -0,0 +1,120 @@
1
+ import { InternalError, ValidationError } from './errors.js';
2
+ import { Result } from './result.js';
3
+ import type { ExecuteTrailOptions } from './execute.js';
4
+ import { getTrailVersionEntryKind } from './trail.js';
5
+ import type { AnyTrail, TrailVersionEntry } from './trail.js';
6
+ import { validateInput, validateOutput } from './validation.js';
7
+
8
+ export type TrailVersionCurrentExecutor<
9
+ Options extends ExecuteTrailOptions = ExecuteTrailOptions,
10
+ > = (
11
+ trail: AnyTrail,
12
+ input: unknown,
13
+ options?: Options
14
+ ) => Promise<Result<unknown, Error>>;
15
+
16
+ const normalizeTransposeError = (
17
+ trail: AnyTrail,
18
+ version: number,
19
+ phase: 'input' | 'output',
20
+ error: unknown
21
+ ): InternalError => {
22
+ const message = error instanceof Error ? error.message : String(error);
23
+ const options: { cause?: Error; context: Record<string, unknown> } = {
24
+ context: { phase, trailId: trail.id, version },
25
+ };
26
+ if (error instanceof Error) {
27
+ options.cause = error;
28
+ }
29
+ return new InternalError(
30
+ `Trail "${trail.id}" version ${version} transpose.${phase} failed: ${message}`,
31
+ options
32
+ );
33
+ };
34
+
35
+ const runTransposeInput = async (
36
+ trail: AnyTrail,
37
+ version: number,
38
+ entry: TrailVersionEntry,
39
+ input: unknown
40
+ ): Promise<Result<unknown, Error>> => {
41
+ if (entry.transpose === undefined) {
42
+ return Result.ok(input);
43
+ }
44
+
45
+ try {
46
+ return Result.ok(await entry.transpose.input({ input }));
47
+ } catch (error: unknown) {
48
+ return Result.err(normalizeTransposeError(trail, version, 'input', error));
49
+ }
50
+ };
51
+
52
+ const runTransposeOutput = async (
53
+ trail: AnyTrail,
54
+ version: number,
55
+ entry: TrailVersionEntry,
56
+ output: unknown
57
+ ): Promise<Result<unknown, Error>> => {
58
+ if (entry.transpose === undefined) {
59
+ return Result.ok(output);
60
+ }
61
+
62
+ try {
63
+ return Result.ok(await entry.transpose.output({ output }));
64
+ } catch (error: unknown) {
65
+ return Result.err(normalizeTransposeError(trail, version, 'output', error));
66
+ }
67
+ };
68
+
69
+ export const executeTrailRevision = async <
70
+ Options extends ExecuteTrailOptions = ExecuteTrailOptions,
71
+ >(
72
+ trail: AnyTrail,
73
+ version: number,
74
+ entry: TrailVersionEntry,
75
+ rawInput: unknown,
76
+ options: Options | undefined,
77
+ executeCurrentTrail: TrailVersionCurrentExecutor<Options>
78
+ ): Promise<Result<unknown, Error>> => {
79
+ if (getTrailVersionEntryKind(entry) !== 'revision') {
80
+ return Result.err(
81
+ new ValidationError(
82
+ `Trail "${trail.id}" version ${version} is not a revision entry`
83
+ )
84
+ );
85
+ }
86
+
87
+ const historicalInput = validateInput(entry.input, rawInput);
88
+ if (historicalInput.isErr()) {
89
+ return historicalInput;
90
+ }
91
+
92
+ const currentInput = await runTransposeInput(
93
+ trail,
94
+ version,
95
+ entry,
96
+ historicalInput.value
97
+ );
98
+ if (currentInput.isErr()) {
99
+ return currentInput;
100
+ }
101
+
102
+ const currentOutput = await executeCurrentTrail(
103
+ trail,
104
+ currentInput.value,
105
+ options
106
+ );
107
+ if (currentOutput.isErr()) {
108
+ return currentOutput;
109
+ }
110
+
111
+ const historicalOutput = await runTransposeOutput(
112
+ trail,
113
+ version,
114
+ entry,
115
+ currentOutput.value
116
+ );
117
+ return historicalOutput.isErr()
118
+ ? historicalOutput
119
+ : validateOutput(entry.output, historicalOutput.value);
120
+ };