@ontrails/core 1.0.0-beta.4 → 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.
Files changed (247) hide show
  1. package/CHANGELOG.md +412 -12
  2. package/README.md +112 -28
  3. package/package.json +11 -1
  4. package/src/activation-provenance.ts +116 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +227 -0
  7. package/src/activation-source.ts +93 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/branded.ts +1 -1
  10. package/src/compose-batch.ts +69 -0
  11. package/src/compose-schema.ts +36 -0
  12. package/src/context.ts +59 -11
  13. package/src/derive.ts +271 -19
  14. package/src/detours.ts +8 -0
  15. package/src/diagnostics.ts +21 -0
  16. package/src/draft.ts +350 -0
  17. package/src/entity.ts +346 -0
  18. package/src/error-projection.ts +51 -0
  19. package/src/errors.ts +335 -48
  20. package/src/execute.ts +1577 -0
  21. package/src/fire.ts +1172 -0
  22. package/src/glob.ts +81 -0
  23. package/src/index.ts +568 -32
  24. package/src/internal/fork-ctx.ts +69 -0
  25. package/src/layer-projection.ts +193 -0
  26. package/src/layer.ts +43 -6
  27. package/src/observe.ts +361 -0
  28. package/src/path-scope.ts +66 -0
  29. package/src/path-security.ts +11 -3
  30. package/src/permits.ts +12 -0
  31. package/src/queue.ts +163 -0
  32. package/src/redaction/patterns.ts +6 -3
  33. package/src/resilience.ts +11 -11
  34. package/src/resource-config.ts +804 -0
  35. package/src/resource.ts +194 -0
  36. package/src/result.ts +18 -0
  37. package/src/run.ts +76 -0
  38. package/src/runtime-builtins.ts +69 -0
  39. package/src/schedule-runtime.ts +689 -0
  40. package/src/schedule.ts +326 -0
  41. package/src/serialization.ts +92 -55
  42. package/src/sha256.ts +136 -0
  43. package/src/signal-diagnostics.ts +633 -0
  44. package/src/signal-ref.ts +111 -0
  45. package/src/signal.ts +104 -0
  46. package/src/store/accessor-protocol.ts +56 -0
  47. package/src/store/index.ts +4 -0
  48. package/src/structured-examples.ts +248 -0
  49. package/src/surface-derivation.ts +91 -0
  50. package/src/surface-filter.ts +101 -0
  51. package/src/surface-overlay.ts +694 -0
  52. package/src/surface-versioning.ts +42 -0
  53. package/src/topo.ts +762 -38
  54. package/src/tracing.ts +346 -0
  55. package/src/trail-id-glob.ts +15 -0
  56. package/src/trail.ts +1244 -34
  57. package/src/trails/derive-trail.ts +835 -0
  58. package/src/trails/index.ts +9 -0
  59. package/src/trails/ingest.ts +152 -0
  60. package/src/trails-db.ts +212 -0
  61. package/src/transport-error-map.ts +174 -0
  62. package/src/type-utils.ts +87 -0
  63. package/src/types.ts +254 -12
  64. package/src/validate-established-topo.ts +73 -0
  65. package/src/validate-topo.ts +569 -55
  66. package/src/validation.ts +152 -4
  67. package/src/version-marker.ts +716 -0
  68. package/src/version-resolution.ts +308 -0
  69. package/src/version-runtime.ts +120 -0
  70. package/src/webhook.ts +461 -0
  71. package/src/workspace.ts +183 -16
  72. package/src/zod-wrappers.ts +72 -0
  73. package/.turbo/turbo-build.log +0 -1
  74. package/.turbo/turbo-lint.log +0 -3
  75. package/.turbo/turbo-typecheck.log +0 -1
  76. package/dist/adapters.d.ts +0 -39
  77. package/dist/adapters.d.ts.map +0 -1
  78. package/dist/adapters.js +0 -2
  79. package/dist/adapters.js.map +0 -1
  80. package/dist/blob-ref.d.ts +0 -20
  81. package/dist/blob-ref.d.ts.map +0 -1
  82. package/dist/blob-ref.js +0 -22
  83. package/dist/blob-ref.js.map +0 -1
  84. package/dist/branded.d.ts +0 -36
  85. package/dist/branded.d.ts.map +0 -1
  86. package/dist/branded.js +0 -89
  87. package/dist/branded.js.map +0 -1
  88. package/dist/collections.d.ts +0 -31
  89. package/dist/collections.d.ts.map +0 -1
  90. package/dist/collections.js +0 -60
  91. package/dist/collections.js.map +0 -1
  92. package/dist/context.d.ts +0 -10
  93. package/dist/context.d.ts.map +0 -1
  94. package/dist/context.js +0 -15
  95. package/dist/context.js.map +0 -1
  96. package/dist/derive.d.ts +0 -33
  97. package/dist/derive.d.ts.map +0 -1
  98. package/dist/derive.js +0 -125
  99. package/dist/derive.js.map +0 -1
  100. package/dist/errors.d.ts +0 -83
  101. package/dist/errors.d.ts.map +0 -1
  102. package/dist/errors.js +0 -142
  103. package/dist/errors.js.map +0 -1
  104. package/dist/event.d.ts +0 -45
  105. package/dist/event.d.ts.map +0 -1
  106. package/dist/event.js +0 -17
  107. package/dist/event.js.map +0 -1
  108. package/dist/fetch.d.ts +0 -15
  109. package/dist/fetch.d.ts.map +0 -1
  110. package/dist/fetch.js +0 -102
  111. package/dist/fetch.js.map +0 -1
  112. package/dist/guards.d.ts +0 -17
  113. package/dist/guards.d.ts.map +0 -1
  114. package/dist/guards.js +0 -25
  115. package/dist/guards.js.map +0 -1
  116. package/dist/health.d.ts +0 -18
  117. package/dist/health.d.ts.map +0 -1
  118. package/dist/health.js +0 -5
  119. package/dist/health.js.map +0 -1
  120. package/dist/index.d.ts +0 -34
  121. package/dist/index.d.ts.map +0 -1
  122. package/dist/index.js +0 -38
  123. package/dist/index.js.map +0 -1
  124. package/dist/job.d.ts +0 -24
  125. package/dist/job.d.ts.map +0 -1
  126. package/dist/job.js +0 -17
  127. package/dist/job.js.map +0 -1
  128. package/dist/layer.d.ts +0 -17
  129. package/dist/layer.d.ts.map +0 -1
  130. package/dist/layer.js +0 -21
  131. package/dist/layer.js.map +0 -1
  132. package/dist/path-security.d.ts +0 -28
  133. package/dist/path-security.d.ts.map +0 -1
  134. package/dist/path-security.js +0 -63
  135. package/dist/path-security.js.map +0 -1
  136. package/dist/patterns/bulk.d.ts +0 -15
  137. package/dist/patterns/bulk.d.ts.map +0 -1
  138. package/dist/patterns/bulk.js +0 -14
  139. package/dist/patterns/bulk.js.map +0 -1
  140. package/dist/patterns/change.d.ts +0 -10
  141. package/dist/patterns/change.d.ts.map +0 -1
  142. package/dist/patterns/change.js +0 -10
  143. package/dist/patterns/change.js.map +0 -1
  144. package/dist/patterns/date-range.d.ts +0 -10
  145. package/dist/patterns/date-range.d.ts.map +0 -1
  146. package/dist/patterns/date-range.js +0 -10
  147. package/dist/patterns/date-range.js.map +0 -1
  148. package/dist/patterns/index.d.ts +0 -9
  149. package/dist/patterns/index.d.ts.map +0 -1
  150. package/dist/patterns/index.js +0 -9
  151. package/dist/patterns/index.js.map +0 -1
  152. package/dist/patterns/pagination.d.ts +0 -18
  153. package/dist/patterns/pagination.d.ts.map +0 -1
  154. package/dist/patterns/pagination.js +0 -18
  155. package/dist/patterns/pagination.js.map +0 -1
  156. package/dist/patterns/progress.d.ts +0 -11
  157. package/dist/patterns/progress.d.ts.map +0 -1
  158. package/dist/patterns/progress.js +0 -11
  159. package/dist/patterns/progress.js.map +0 -1
  160. package/dist/patterns/sorting.d.ts +0 -13
  161. package/dist/patterns/sorting.d.ts.map +0 -1
  162. package/dist/patterns/sorting.js +0 -10
  163. package/dist/patterns/sorting.js.map +0 -1
  164. package/dist/patterns/status.d.ts +0 -15
  165. package/dist/patterns/status.d.ts.map +0 -1
  166. package/dist/patterns/status.js +0 -9
  167. package/dist/patterns/status.js.map +0 -1
  168. package/dist/patterns/timestamps.d.ts +0 -10
  169. package/dist/patterns/timestamps.d.ts.map +0 -1
  170. package/dist/patterns/timestamps.js +0 -10
  171. package/dist/patterns/timestamps.js.map +0 -1
  172. package/dist/redaction/index.d.ts +0 -4
  173. package/dist/redaction/index.d.ts.map +0 -1
  174. package/dist/redaction/index.js +0 -3
  175. package/dist/redaction/index.js.map +0 -1
  176. package/dist/redaction/patterns.d.ts +0 -9
  177. package/dist/redaction/patterns.d.ts.map +0 -1
  178. package/dist/redaction/patterns.js +0 -39
  179. package/dist/redaction/patterns.js.map +0 -1
  180. package/dist/redaction/redactor.d.ts +0 -27
  181. package/dist/redaction/redactor.d.ts.map +0 -1
  182. package/dist/redaction/redactor.js +0 -89
  183. package/dist/redaction/redactor.js.map +0 -1
  184. package/dist/resilience.d.ts +0 -34
  185. package/dist/resilience.d.ts.map +0 -1
  186. package/dist/resilience.js +0 -164
  187. package/dist/resilience.js.map +0 -1
  188. package/dist/result.d.ts +0 -57
  189. package/dist/result.d.ts.map +0 -1
  190. package/dist/result.js +0 -156
  191. package/dist/result.js.map +0 -1
  192. package/dist/serialization.d.ts +0 -27
  193. package/dist/serialization.d.ts.map +0 -1
  194. package/dist/serialization.js +0 -153
  195. package/dist/serialization.js.map +0 -1
  196. package/dist/topo.d.ts +0 -16
  197. package/dist/topo.d.ts.map +0 -1
  198. package/dist/topo.js +0 -66
  199. package/dist/topo.js.map +0 -1
  200. package/dist/trail.d.ts +0 -89
  201. package/dist/trail.d.ts.map +0 -1
  202. package/dist/trail.js +0 -18
  203. package/dist/trail.js.map +0 -1
  204. package/dist/types.d.ts +0 -46
  205. package/dist/types.d.ts.map +0 -1
  206. package/dist/types.js +0 -2
  207. package/dist/types.js.map +0 -1
  208. package/dist/validate-topo.d.ts +0 -24
  209. package/dist/validate-topo.d.ts.map +0 -1
  210. package/dist/validate-topo.js +0 -158
  211. package/dist/validate-topo.js.map +0 -1
  212. package/dist/validation.d.ts +0 -27
  213. package/dist/validation.d.ts.map +0 -1
  214. package/dist/validation.js +0 -134
  215. package/dist/validation.js.map +0 -1
  216. package/dist/workspace.d.ts +0 -25
  217. package/dist/workspace.d.ts.map +0 -1
  218. package/dist/workspace.js +0 -57
  219. package/dist/workspace.js.map +0 -1
  220. package/src/__tests__/blob-ref.test.ts +0 -103
  221. package/src/__tests__/branded.test.ts +0 -148
  222. package/src/__tests__/collections.test.ts +0 -126
  223. package/src/__tests__/context.test.ts +0 -66
  224. package/src/__tests__/derive.test.ts +0 -203
  225. package/src/__tests__/errors.test.ts +0 -309
  226. package/src/__tests__/event.test.ts +0 -82
  227. package/src/__tests__/fetch.test.ts +0 -217
  228. package/src/__tests__/guards.test.ts +0 -102
  229. package/src/__tests__/job.test.ts +0 -98
  230. package/src/__tests__/layer.test.ts +0 -212
  231. package/src/__tests__/path-security.test.ts +0 -114
  232. package/src/__tests__/patterns.test.ts +0 -273
  233. package/src/__tests__/redaction.test.ts +0 -244
  234. package/src/__tests__/resilience.test.ts +0 -246
  235. package/src/__tests__/result.test.ts +0 -155
  236. package/src/__tests__/serialization.test.ts +0 -401
  237. package/src/__tests__/topo.test.ts +0 -181
  238. package/src/__tests__/trail.test.ts +0 -217
  239. package/src/__tests__/validate-topo.test.ts +0 -278
  240. package/src/__tests__/validation.test.ts +0 -283
  241. package/src/__tests__/workspace.test.ts +0 -183
  242. package/src/adapters.ts +0 -68
  243. package/src/event.ts +0 -77
  244. package/src/health.ts +0 -23
  245. package/src/job.ts +0 -20
  246. package/tsconfig.json +0 -9
  247. package/tsconfig.tsbuildinfo +0 -1
@@ -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
+ };