@ontrails/core 1.0.0-beta.14 → 1.0.0-beta.16

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 (314) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +73 -24
  3. package/package.json +11 -4
  4. package/src/activation-provenance.ts +111 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +221 -0
  7. package/src/activation-source.ts +91 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/branded.ts +1 -1
  10. package/src/context.ts +28 -4
  11. package/src/contour.ts +344 -0
  12. package/src/cross-batch.ts +69 -0
  13. package/src/cross-schema.ts +36 -0
  14. package/src/detours.ts +8 -0
  15. package/src/draft.ts +60 -52
  16. package/src/error-projection.ts +51 -0
  17. package/src/errors.ts +245 -48
  18. package/src/execute.ts +1250 -40
  19. package/src/fire.ts +1169 -0
  20. package/src/index.ts +383 -49
  21. package/src/internal/fork-ctx.ts +69 -0
  22. package/src/layer-projection.ts +192 -0
  23. package/src/layer.ts +81 -0
  24. package/src/observe.ts +361 -0
  25. package/src/path-security.ts +1 -1
  26. package/src/permits.ts +12 -0
  27. package/src/redaction/patterns.ts +6 -3
  28. package/src/resilience.ts +11 -11
  29. package/src/resource-config.ts +792 -0
  30. package/src/resource.ts +175 -0
  31. package/src/result.ts +18 -0
  32. package/src/run.ts +7 -6
  33. package/src/schedule-runtime.ts +689 -0
  34. package/src/schedule.ts +324 -0
  35. package/src/serialization.ts +90 -55
  36. package/src/signal-diagnostics.ts +633 -0
  37. package/src/signal-ref.ts +87 -0
  38. package/src/signal.ts +37 -0
  39. package/src/store/accessor-protocol.ts +56 -0
  40. package/src/store/index.ts +4 -0
  41. package/src/structured-examples.ts +245 -0
  42. package/src/surface-derivation.ts +91 -0
  43. package/src/surface-filter.ts +176 -0
  44. package/src/topo.ts +710 -59
  45. package/src/tracing.ts +345 -0
  46. package/src/trail.ts +347 -33
  47. package/src/trails/derive-trail.ts +842 -0
  48. package/src/trails/index.ts +9 -0
  49. package/src/trails/ingest.ts +138 -0
  50. package/src/{internal/trails-db.ts → trails-db.ts} +50 -31
  51. package/src/transport-error-map.ts +174 -0
  52. package/src/type-utils.ts +17 -2
  53. package/src/types.ts +208 -17
  54. package/src/validate-established-topo.ts +8 -2
  55. package/src/validate-topo.ts +244 -13
  56. package/src/validation.ts +58 -1
  57. package/src/webhook.ts +276 -0
  58. package/src/workspace.ts +1 -1
  59. package/src/zod-wrappers.ts +72 -0
  60. package/.turbo/turbo-build.log +0 -1
  61. package/.turbo/turbo-lint.log +0 -3
  62. package/.turbo/turbo-typecheck.log +0 -1
  63. package/dist/adapters.d.ts +0 -39
  64. package/dist/adapters.d.ts.map +0 -1
  65. package/dist/adapters.js +0 -2
  66. package/dist/adapters.js.map +0 -1
  67. package/dist/blob-ref.d.ts +0 -20
  68. package/dist/blob-ref.d.ts.map +0 -1
  69. package/dist/blob-ref.js +0 -22
  70. package/dist/blob-ref.js.map +0 -1
  71. package/dist/branded.d.ts +0 -36
  72. package/dist/branded.d.ts.map +0 -1
  73. package/dist/branded.js +0 -89
  74. package/dist/branded.js.map +0 -1
  75. package/dist/collections.d.ts +0 -31
  76. package/dist/collections.d.ts.map +0 -1
  77. package/dist/collections.js +0 -60
  78. package/dist/collections.js.map +0 -1
  79. package/dist/context.d.ts +0 -10
  80. package/dist/context.d.ts.map +0 -1
  81. package/dist/context.js +0 -21
  82. package/dist/context.js.map +0 -1
  83. package/dist/derive.d.ts +0 -44
  84. package/dist/derive.d.ts.map +0 -1
  85. package/dist/derive.js +0 -149
  86. package/dist/derive.js.map +0 -1
  87. package/dist/dispatch.d.ts +0 -27
  88. package/dist/dispatch.d.ts.map +0 -1
  89. package/dist/dispatch.js +0 -34
  90. package/dist/dispatch.js.map +0 -1
  91. package/dist/draft.d.ts +0 -28
  92. package/dist/draft.d.ts.map +0 -1
  93. package/dist/draft.js +0 -156
  94. package/dist/draft.js.map +0 -1
  95. package/dist/errors.d.ts +0 -83
  96. package/dist/errors.d.ts.map +0 -1
  97. package/dist/errors.js +0 -142
  98. package/dist/errors.js.map +0 -1
  99. package/dist/event.d.ts +0 -8
  100. package/dist/event.d.ts.map +0 -1
  101. package/dist/event.js +0 -7
  102. package/dist/event.js.map +0 -1
  103. package/dist/execute.d.ts +0 -35
  104. package/dist/execute.d.ts.map +0 -1
  105. package/dist/execute.js +0 -90
  106. package/dist/execute.js.map +0 -1
  107. package/dist/fetch.d.ts +0 -15
  108. package/dist/fetch.d.ts.map +0 -1
  109. package/dist/fetch.js +0 -102
  110. package/dist/fetch.js.map +0 -1
  111. package/dist/gate.d.ts +0 -17
  112. package/dist/gate.d.ts.map +0 -1
  113. package/dist/gate.js +0 -21
  114. package/dist/gate.js.map +0 -1
  115. package/dist/guards.d.ts +0 -17
  116. package/dist/guards.d.ts.map +0 -1
  117. package/dist/guards.js +0 -25
  118. package/dist/guards.js.map +0 -1
  119. package/dist/health.d.ts +0 -18
  120. package/dist/health.d.ts.map +0 -1
  121. package/dist/health.js +0 -5
  122. package/dist/health.js.map +0 -1
  123. package/dist/index.d.ts +0 -48
  124. package/dist/index.d.ts.map +0 -1
  125. package/dist/index.js +0 -51
  126. package/dist/index.js.map +0 -1
  127. package/dist/internal/topo-saves.d.ts +0 -47
  128. package/dist/internal/topo-saves.d.ts.map +0 -1
  129. package/dist/internal/topo-saves.js +0 -310
  130. package/dist/internal/topo-saves.js.map +0 -1
  131. package/dist/internal/topo-store-read.d.ts +0 -67
  132. package/dist/internal/topo-store-read.d.ts.map +0 -1
  133. package/dist/internal/topo-store-read.js +0 -222
  134. package/dist/internal/topo-store-read.js.map +0 -1
  135. package/dist/internal/topo-store.d.ts +0 -12
  136. package/dist/internal/topo-store.d.ts.map +0 -1
  137. package/dist/internal/topo-store.js +0 -571
  138. package/dist/internal/topo-store.js.map +0 -1
  139. package/dist/internal/trails-db.d.ts +0 -16
  140. package/dist/internal/trails-db.d.ts.map +0 -1
  141. package/dist/internal/trails-db.js +0 -118
  142. package/dist/internal/trails-db.js.map +0 -1
  143. package/dist/job.d.ts +0 -24
  144. package/dist/job.d.ts.map +0 -1
  145. package/dist/job.js +0 -17
  146. package/dist/job.js.map +0 -1
  147. package/dist/layer.d.ts +0 -6
  148. package/dist/layer.d.ts.map +0 -1
  149. package/dist/layer.js +0 -4
  150. package/dist/layer.js.map +0 -1
  151. package/dist/path-security.d.ts +0 -28
  152. package/dist/path-security.d.ts.map +0 -1
  153. package/dist/path-security.js +0 -63
  154. package/dist/path-security.js.map +0 -1
  155. package/dist/patterns/bulk.d.ts +0 -15
  156. package/dist/patterns/bulk.d.ts.map +0 -1
  157. package/dist/patterns/bulk.js +0 -14
  158. package/dist/patterns/bulk.js.map +0 -1
  159. package/dist/patterns/change.d.ts +0 -10
  160. package/dist/patterns/change.d.ts.map +0 -1
  161. package/dist/patterns/change.js +0 -10
  162. package/dist/patterns/change.js.map +0 -1
  163. package/dist/patterns/date-range.d.ts +0 -10
  164. package/dist/patterns/date-range.d.ts.map +0 -1
  165. package/dist/patterns/date-range.js +0 -10
  166. package/dist/patterns/date-range.js.map +0 -1
  167. package/dist/patterns/index.d.ts +0 -9
  168. package/dist/patterns/index.d.ts.map +0 -1
  169. package/dist/patterns/index.js +0 -9
  170. package/dist/patterns/index.js.map +0 -1
  171. package/dist/patterns/pagination.d.ts +0 -18
  172. package/dist/patterns/pagination.d.ts.map +0 -1
  173. package/dist/patterns/pagination.js +0 -18
  174. package/dist/patterns/pagination.js.map +0 -1
  175. package/dist/patterns/progress.d.ts +0 -11
  176. package/dist/patterns/progress.d.ts.map +0 -1
  177. package/dist/patterns/progress.js +0 -11
  178. package/dist/patterns/progress.js.map +0 -1
  179. package/dist/patterns/sorting.d.ts +0 -13
  180. package/dist/patterns/sorting.d.ts.map +0 -1
  181. package/dist/patterns/sorting.js +0 -10
  182. package/dist/patterns/sorting.js.map +0 -1
  183. package/dist/patterns/status.d.ts +0 -15
  184. package/dist/patterns/status.d.ts.map +0 -1
  185. package/dist/patterns/status.js +0 -9
  186. package/dist/patterns/status.js.map +0 -1
  187. package/dist/patterns/timestamps.d.ts +0 -10
  188. package/dist/patterns/timestamps.d.ts.map +0 -1
  189. package/dist/patterns/timestamps.js +0 -10
  190. package/dist/patterns/timestamps.js.map +0 -1
  191. package/dist/provision-config.d.ts +0 -22
  192. package/dist/provision-config.d.ts.map +0 -1
  193. package/dist/provision-config.js +0 -210
  194. package/dist/provision-config.js.map +0 -1
  195. package/dist/provision.d.ts +0 -71
  196. package/dist/provision.d.ts.map +0 -1
  197. package/dist/provision.js +0 -56
  198. package/dist/provision.js.map +0 -1
  199. package/dist/redaction/index.d.ts +0 -4
  200. package/dist/redaction/index.d.ts.map +0 -1
  201. package/dist/redaction/index.js +0 -3
  202. package/dist/redaction/index.js.map +0 -1
  203. package/dist/redaction/patterns.d.ts +0 -9
  204. package/dist/redaction/patterns.d.ts.map +0 -1
  205. package/dist/redaction/patterns.js +0 -39
  206. package/dist/redaction/patterns.js.map +0 -1
  207. package/dist/redaction/redactor.d.ts +0 -27
  208. package/dist/redaction/redactor.d.ts.map +0 -1
  209. package/dist/redaction/redactor.js +0 -89
  210. package/dist/redaction/redactor.js.map +0 -1
  211. package/dist/resilience.d.ts +0 -34
  212. package/dist/resilience.d.ts.map +0 -1
  213. package/dist/resilience.js +0 -164
  214. package/dist/resilience.js.map +0 -1
  215. package/dist/result.d.ts +0 -57
  216. package/dist/result.d.ts.map +0 -1
  217. package/dist/result.js +0 -156
  218. package/dist/result.js.map +0 -1
  219. package/dist/run.d.ts +0 -27
  220. package/dist/run.d.ts.map +0 -1
  221. package/dist/run.js +0 -34
  222. package/dist/run.js.map +0 -1
  223. package/dist/serialization.d.ts +0 -27
  224. package/dist/serialization.d.ts.map +0 -1
  225. package/dist/serialization.js +0 -153
  226. package/dist/serialization.js.map +0 -1
  227. package/dist/service-config.d.ts +0 -22
  228. package/dist/service-config.d.ts.map +0 -1
  229. package/dist/service-config.js +0 -210
  230. package/dist/service-config.js.map +0 -1
  231. package/dist/service.d.ts +0 -71
  232. package/dist/service.d.ts.map +0 -1
  233. package/dist/service.js +0 -56
  234. package/dist/service.js.map +0 -1
  235. package/dist/signal.d.ts +0 -33
  236. package/dist/signal.d.ts.map +0 -1
  237. package/dist/signal.js +0 -17
  238. package/dist/signal.js.map +0 -1
  239. package/dist/topo-store.d.ts +0 -48
  240. package/dist/topo-store.d.ts.map +0 -1
  241. package/dist/topo-store.js +0 -175
  242. package/dist/topo-store.js.map +0 -1
  243. package/dist/topo.d.ts +0 -25
  244. package/dist/topo.d.ts.map +0 -1
  245. package/dist/topo.js +0 -112
  246. package/dist/topo.js.map +0 -1
  247. package/dist/trail.d.ts +0 -96
  248. package/dist/trail.d.ts.map +0 -1
  249. package/dist/trail.js +0 -20
  250. package/dist/trail.js.map +0 -1
  251. package/dist/type-utils.d.ts +0 -24
  252. package/dist/type-utils.d.ts.map +0 -1
  253. package/dist/type-utils.js +0 -12
  254. package/dist/type-utils.js.map +0 -1
  255. package/dist/types.d.ts +0 -71
  256. package/dist/types.d.ts.map +0 -1
  257. package/dist/types.js +0 -3
  258. package/dist/types.js.map +0 -1
  259. package/dist/validate-established-topo.d.ts +0 -76
  260. package/dist/validate-established-topo.d.ts.map +0 -1
  261. package/dist/validate-established-topo.js +0 -43
  262. package/dist/validate-established-topo.js.map +0 -1
  263. package/dist/validate-topo.d.ts +0 -24
  264. package/dist/validate-topo.d.ts.map +0 -1
  265. package/dist/validate-topo.js +0 -176
  266. package/dist/validate-topo.js.map +0 -1
  267. package/dist/validation.d.ts +0 -27
  268. package/dist/validation.d.ts.map +0 -1
  269. package/dist/validation.js +0 -165
  270. package/dist/validation.js.map +0 -1
  271. package/dist/workspace.d.ts +0 -25
  272. package/dist/workspace.d.ts.map +0 -1
  273. package/dist/workspace.js +0 -57
  274. package/dist/workspace.js.map +0 -1
  275. package/src/__tests__/blob-ref.test.ts +0 -103
  276. package/src/__tests__/branded.test.ts +0 -148
  277. package/src/__tests__/collections.test.ts +0 -126
  278. package/src/__tests__/context.test.ts +0 -77
  279. package/src/__tests__/derive.test.ts +0 -260
  280. package/src/__tests__/errors.test.ts +0 -309
  281. package/src/__tests__/execute.test.ts +0 -525
  282. package/src/__tests__/fetch.test.ts +0 -217
  283. package/src/__tests__/gate.test.ts +0 -125
  284. package/src/__tests__/guards.test.ts +0 -102
  285. package/src/__tests__/path-security.test.ts +0 -114
  286. package/src/__tests__/patterns.test.ts +0 -273
  287. package/src/__tests__/redaction.test.ts +0 -244
  288. package/src/__tests__/resilience.test.ts +0 -246
  289. package/src/__tests__/result.test.ts +0 -155
  290. package/src/__tests__/run.test.ts +0 -181
  291. package/src/__tests__/serialization.test.ts +0 -401
  292. package/src/__tests__/service-config.test.ts +0 -228
  293. package/src/__tests__/service.test.ts +0 -197
  294. package/src/__tests__/signal.test.ts +0 -82
  295. package/src/__tests__/topo-store-read.test.ts +0 -251
  296. package/src/__tests__/topo-store.test.ts +0 -469
  297. package/src/__tests__/topo.test.ts +0 -277
  298. package/src/__tests__/trail-permit.test.ts +0 -60
  299. package/src/__tests__/trail.test.ts +0 -261
  300. package/src/__tests__/trails-db.test.ts +0 -191
  301. package/src/__tests__/type-utils.test.ts +0 -90
  302. package/src/__tests__/validate-topo.test.ts +0 -489
  303. package/src/__tests__/validation.test.ts +0 -336
  304. package/src/__tests__/workspace.test.ts +0 -183
  305. package/src/event.ts +0 -15
  306. package/src/gate.ts +0 -44
  307. package/src/internal/topo-saves.ts +0 -429
  308. package/src/internal/topo-store-read.ts +0 -473
  309. package/src/internal/topo-store.ts +0 -1087
  310. package/src/provision-config.ts +0 -373
  311. package/src/provision.ts +0 -148
  312. package/src/topo-store.ts +0 -301
  313. package/tsconfig.json +0 -9
  314. package/tsconfig.tsbuildinfo +0 -1
@@ -1,525 +0,0 @@
1
- /* oxlint-disable require-await -- trail implementations satisfy async interface without awaiting */
2
- import { describe, test, expect } from 'bun:test';
3
-
4
- import { z } from 'zod';
5
-
6
- import { InternalError, ValidationError } from '../errors';
7
- import { executeTrail } from '../execute';
8
- import { createTrailContext } from '../context';
9
- import type { Gate } from '../gate';
10
- import { Result } from '../result';
11
- import { provision } from '../provision';
12
- import { trail } from '../trail';
13
- import type { TrailContext, TrailContextInit } from '../types';
14
-
15
- // ---------------------------------------------------------------------------
16
- // Fixtures
17
- // ---------------------------------------------------------------------------
18
-
19
- const echoTrail = trail('echo', {
20
- blaze: (input) => Result.ok({ value: input.value }),
21
- input: z.object({ value: z.string() }),
22
- output: z.object({ value: z.string() }),
23
- });
24
-
25
- const failingTrail = trail('fails', {
26
- blaze: () => Result.err(new ValidationError('bad input')),
27
- input: z.object({}),
28
- });
29
-
30
- const throwingTrail = trail('throws', {
31
- blaze: () => {
32
- throw new Error('kaboom');
33
- },
34
- input: z.object({}),
35
- });
36
-
37
- const nextProvisionId = (name: string): string =>
38
- `test.provision.${name}.${Bun.randomUUIDv7()}`;
39
-
40
- const createResolvedValueProvision = (
41
- id: string,
42
- onCreate: () => void,
43
- value: number
44
- ) =>
45
- provision(id, {
46
- create: () => {
47
- onCreate();
48
- return Result.ok({ value });
49
- },
50
- });
51
-
52
- const createEagerProvisionTrail = (
53
- id: string,
54
- counter: ReturnType<typeof createResolvedValueProvision>,
55
- onRun: (value: number) => void
56
- ) =>
57
- trail('provision.eager', {
58
- blaze: (_input, ctx) => {
59
- const fromAccessor = ctx.provision<{ value: number }>(id);
60
- const fromDefinition = counter.from(ctx);
61
- onRun(fromDefinition.value);
62
- return Result.ok({ total: fromAccessor.value + 1 });
63
- },
64
- input: z.object({}),
65
- output: z.object({ total: z.number() }),
66
- provisions: [counter],
67
- });
68
-
69
- const createProvisionProbeGate = (
70
- counter: ReturnType<typeof createResolvedValueProvision>,
71
- onResolve: (value: number) => void
72
- ): Gate => ({
73
- name: 'uses-provision',
74
- wrap(_trail, impl) {
75
- return async (input, ctx) => {
76
- onResolve(ctx.provision<{ value: number }>(counter).value);
77
- return await impl(input, ctx);
78
- };
79
- },
80
- });
81
-
82
- const createSingletonProvision = (id: string, onCreate: () => number) =>
83
- provision(id, {
84
- create: () => Result.ok({ createdAtCall: onCreate() }),
85
- });
86
-
87
- const createSingletonTrail = (
88
- singleton: ReturnType<typeof createSingletonProvision>
89
- ) =>
90
- trail('provision.singleton', {
91
- blaze: (_input, ctx) =>
92
- Result.ok({
93
- createdAtCall: singleton.from(ctx).createdAtCall,
94
- }),
95
- input: z.object({}),
96
- output: z.object({ createdAtCall: z.number() }),
97
- provisions: [singleton],
98
- });
99
-
100
- const unwrapExecution = async (
101
- target: ReturnType<typeof createSingletonTrail>
102
- ) => {
103
- const result = await executeTrail(target, {});
104
- return result.unwrap();
105
- };
106
-
107
- // ---------------------------------------------------------------------------
108
- // Tests
109
- // ---------------------------------------------------------------------------
110
-
111
- describe('executeTrail', () => {
112
- describe('happy path', () => {
113
- test('validates input and executes trail', async () => {
114
- const result = await executeTrail(echoTrail, { value: 'hello' });
115
-
116
- expect(result.isOk()).toBe(true);
117
- expect(result.unwrap()).toEqual({ value: 'hello' });
118
- });
119
-
120
- test('eagerly resolves declared provisions before gates and implementation run', async () => {
121
- const id = nextProvisionId('eager');
122
- const captures = {
123
- createCalls: 0,
124
- gateResolved: undefined as number | undefined,
125
- runResolved: undefined as number | undefined,
126
- };
127
- const counter = createResolvedValueProvision(
128
- id,
129
- () => {
130
- captures.createCalls += 1;
131
- },
132
- 41
133
- );
134
- const layeredTrail = createEagerProvisionTrail(id, counter, (value) => {
135
- captures.runResolved = value;
136
- });
137
- const gate = createProvisionProbeGate(counter, (value) => {
138
- captures.gateResolved = value;
139
- });
140
-
141
- const result = await executeTrail(layeredTrail, {}, { gates: [gate] });
142
-
143
- expect(result.unwrap()).toEqual({ total: 42 });
144
- expect(captures.createCalls).toBe(1);
145
- expect(captures.gateResolved).toBe(41);
146
- expect(captures.runResolved).toBe(41);
147
- });
148
-
149
- test('awaits async provision factories before running the trail', async () => {
150
- const db = provision(nextProvisionId('async-factory'), {
151
- create: async () => {
152
- await Bun.sleep(0);
153
- return Result.ok({ source: 'async-factory' });
154
- },
155
- });
156
- const provisionTrail = trail('provision.async-factory', {
157
- blaze: (_input, ctx) =>
158
- Result.ok({ source: db.from(ctx).source as string }),
159
- input: z.object({}),
160
- output: z.object({ source: z.string() }),
161
- provisions: [db],
162
- });
163
-
164
- const result = await executeTrail(provisionTrail, {});
165
-
166
- expect(result.isOk()).toBe(true);
167
- expect(result.unwrap()).toEqual({ source: 'async-factory' });
168
- });
169
-
170
- test('reuses cached singleton provisions across executions', async () => {
171
- const id = nextProvisionId('singleton');
172
- const captures = { createCalls: 0 };
173
- const singleton = createSingletonProvision(id, () => {
174
- captures.createCalls += 1;
175
- return captures.createCalls;
176
- });
177
- const singletonTrail = createSingletonTrail(singleton);
178
-
179
- const outputs = [
180
- await unwrapExecution(singletonTrail),
181
- await unwrapExecution(singletonTrail),
182
- ];
183
-
184
- expect(outputs).toEqual([{ createdAtCall: 1 }, { createdAtCall: 1 }]);
185
- expect(captures.createCalls).toBe(1);
186
- });
187
-
188
- test('scopes cached singleton provisions to compatible provision contexts', async () => {
189
- const id = nextProvisionId('singleton-context');
190
- const envAwareProvision = provision(id, {
191
- create: (ctx) => Result.ok({ value: String(ctx.env?.VAL) }),
192
- });
193
- const envAwareTrail = trail('provision.singleton-context', {
194
- blaze: (_input, ctx) =>
195
- Result.ok({ value: envAwareProvision.from(ctx).value }),
196
- input: z.object({}),
197
- output: z.object({ value: z.string() }),
198
- provisions: [envAwareProvision],
199
- });
200
-
201
- const first = await executeTrail(
202
- envAwareTrail,
203
- {},
204
- {
205
- createContext: () =>
206
- createTrailContext({
207
- env: { VAL: 'first' },
208
- }),
209
- }
210
- );
211
- const second = await executeTrail(
212
- envAwareTrail,
213
- {},
214
- {
215
- createContext: () =>
216
- createTrailContext({
217
- env: { VAL: 'second' },
218
- }),
219
- }
220
- );
221
-
222
- expect(first.unwrap()).toEqual({ value: 'first' });
223
- expect(second.unwrap()).toEqual({ value: 'second' });
224
- });
225
- });
226
-
227
- describe('validation', () => {
228
- test('returns validation error for invalid input', async () => {
229
- const result = await executeTrail(echoTrail, { value: 42 });
230
-
231
- expect(result.isErr()).toBe(true);
232
- expect(result.error).toBeInstanceOf(ValidationError);
233
- });
234
- });
235
-
236
- describe('gates', () => {
237
- test('composes gates around execution', async () => {
238
- const log: string[] = [];
239
- const gate: Gate = {
240
- name: 'test-gate',
241
- wrap(_trail, impl) {
242
- return async (input, ctx) => {
243
- log.push('before');
244
- const r = await impl(input, ctx);
245
- log.push('after');
246
- return r;
247
- };
248
- },
249
- };
250
-
251
- const result = await executeTrail(
252
- echoTrail,
253
- { value: 'x' },
254
- { gates: [gate] }
255
- );
256
-
257
- expect(result.isOk()).toBe(true);
258
- expect(log).toEqual(['before', 'after']);
259
- });
260
- });
261
-
262
- describe('context', () => {
263
- test('accepts context overrides', async () => {
264
- let capturedCtx: TrailContext | undefined;
265
- const ctxTrail = trail('ctx-test', {
266
- blaze: (_input, ctx) => {
267
- capturedCtx = ctx;
268
- return Result.ok(null);
269
- },
270
- input: z.object({}),
271
- });
272
-
273
- await executeTrail(ctxTrail, {}, { ctx: { requestId: 'override-id' } });
274
-
275
- expect(capturedCtx?.requestId).toBe('override-id');
276
- });
277
-
278
- test('accepts abortSignal override', async () => {
279
- let capturedSignal: AbortSignal | undefined;
280
- const sigTrail = trail('sig-test', {
281
- blaze: (_input, ctx) => {
282
- capturedSignal = ctx.abortSignal;
283
- return Result.ok(null);
284
- },
285
- input: z.object({}),
286
- });
287
-
288
- const abortSignal = AbortSignal.timeout(9999);
289
- await executeTrail(sigTrail, {}, { abortSignal });
290
-
291
- expect(capturedSignal).toBe(abortSignal);
292
- });
293
-
294
- test('accepts context factory', async () => {
295
- let capturedCtx: TrailContext | undefined;
296
- const ctxTrail = trail('factory-test', {
297
- blaze: (_input, ctx) => {
298
- capturedCtx = ctx;
299
- return Result.ok(null);
300
- },
301
- input: z.object({}),
302
- });
303
-
304
- const customCtx: TrailContextInit = {
305
- abortSignal: new AbortController().signal,
306
- cwd: '/custom',
307
- requestId: 'factory-id',
308
- };
309
-
310
- await executeTrail(ctxTrail, {}, { createContext: () => customCtx });
311
-
312
- expect(capturedCtx?.requestId).toBe('factory-id');
313
- expect(capturedCtx?.cwd).toBe('/custom');
314
- });
315
-
316
- test('context factory + ctx overrides merge correctly', async () => {
317
- let capturedCtx: TrailContext | undefined;
318
- const ctxTrail = trail('merge-test', {
319
- blaze: (_input, ctx) => {
320
- capturedCtx = ctx;
321
- return Result.ok(null);
322
- },
323
- input: z.object({}),
324
- });
325
-
326
- const baseCtx: TrailContextInit = {
327
- abortSignal: new AbortController().signal,
328
- cwd: '/factory',
329
- requestId: 'factory-id',
330
- };
331
-
332
- await executeTrail(
333
- ctxTrail,
334
- {},
335
- {
336
- createContext: () => baseCtx,
337
- ctx: { requestId: 'overridden-id' },
338
- }
339
- );
340
-
341
- expect(capturedCtx?.requestId).toBe('overridden-id');
342
- expect(capturedCtx?.cwd).toBe('/factory');
343
- });
344
-
345
- test('deep-merges extensions from factory and overrides', async () => {
346
- let captured: TrailContext | undefined;
347
- const t = trail('ext.test', {
348
- blaze: (_input, ctx) => {
349
- captured = ctx;
350
- return Result.ok({});
351
- },
352
- input: z.object({}),
353
- output: z.object({}),
354
- });
355
- await executeTrail(
356
- t,
357
- {},
358
- {
359
- createContext: () =>
360
- createTrailContext({ extensions: { store: 'db' } }),
361
- ctx: { extensions: { userId: '123' } },
362
- }
363
- );
364
- expect(captured?.extensions).toEqual({ store: 'db', userId: '123' });
365
- });
366
-
367
- test('rebinds ctx.provision after merging extension overrides from createContext', async () => {
368
- let resolvedSource: string | undefined;
369
- const db = provision(nextProvisionId('context-override'), {
370
- create: () => Result.ok({ source: 'factory' }),
371
- });
372
- const provisionTrail = trail('ctx.provision.override', {
373
- blaze: (_input, ctx) => {
374
- resolvedSource = ctx.provision<{ source: string }>(db).source;
375
- return Result.ok({ source: resolvedSource });
376
- },
377
- input: z.object({}),
378
- output: z.object({ source: z.string() }),
379
- provisions: [db],
380
- });
381
-
382
- const result = await executeTrail(
383
- provisionTrail,
384
- {},
385
- {
386
- createContext: () =>
387
- createTrailContext({
388
- extensions: {
389
- [db.id]: { source: 'factory-context' },
390
- },
391
- }),
392
- ctx: {
393
- extensions: {
394
- [db.id]: { source: 'override-context' },
395
- },
396
- },
397
- }
398
- );
399
-
400
- expect(result.unwrap()).toEqual({ source: 'override-context' });
401
- expect(resolvedSource).toBe('override-context');
402
- });
403
-
404
- test('context factory seeds the provision accessor when omitted', async () => {
405
- let capturedCtx: TrailContext | undefined;
406
- const id = nextProvisionId('factory-seed');
407
- const widget = { id: 'widget-1' };
408
- const widgetTrail = trail('provision.factory-seed', {
409
- blaze: (_input, ctx) => {
410
- capturedCtx = ctx;
411
- return Result.ok(null);
412
- },
413
- input: z.object({}),
414
- provisions: [],
415
- });
416
-
417
- await executeTrail(
418
- widgetTrail,
419
- {},
420
- {
421
- createContext: () => ({
422
- abortSignal: new AbortController().signal,
423
- extensions: { [id]: widget },
424
- requestId: 'seeded-provision',
425
- }),
426
- }
427
- );
428
-
429
- expect(capturedCtx?.provision(id)).toBe(widget);
430
- });
431
- });
432
-
433
- describe('error handling', () => {
434
- test('propagates Result.err from run function', async () => {
435
- const result = await executeTrail(failingTrail, {});
436
-
437
- expect(result.isErr()).toBe(true);
438
- expect(result.error).toBeInstanceOf(ValidationError);
439
- expect(result.error.message).toBe('bad input');
440
- });
441
-
442
- test('catches thrown exceptions and returns InternalError', async () => {
443
- const result = await executeTrail(throwingTrail, {});
444
-
445
- expect(result.isErr()).toBe(true);
446
- expect(result.error).toBeInstanceOf(InternalError);
447
- expect(result.error.message).toBe('kaboom');
448
- });
449
-
450
- test('short-circuits when a provision factory returns Result.err', async () => {
451
- const failingProvision = provision(nextProvisionId('factory-error'), {
452
- create: () =>
453
- Result.err(new ValidationError('DATABASE_URL is required')),
454
- });
455
- let ran = false;
456
- const provisionTrail = trail('provision.factory-error', {
457
- blaze: () => {
458
- ran = true;
459
- return Result.ok(null);
460
- },
461
- input: z.object({}),
462
- provisions: [failingProvision],
463
- });
464
-
465
- const result = await executeTrail(provisionTrail, {});
466
-
467
- expect(result.isErr()).toBe(true);
468
- expect(result.error).toBeInstanceOf(ValidationError);
469
- expect(result.error.message).toBe('DATABASE_URL is required');
470
- expect(ran).toBe(false);
471
- });
472
-
473
- test('wraps thrown provision factory exceptions with the provision ID in context', async () => {
474
- const explodingProvision = provision(nextProvisionId('factory-throw'), {
475
- create: () => {
476
- throw new Error('boom');
477
- },
478
- });
479
- const provisionTrail = trail('provision.factory-throw', {
480
- blaze: () => Result.ok(null),
481
- input: z.object({}),
482
- provisions: [explodingProvision],
483
- });
484
-
485
- const result = await executeTrail(provisionTrail, {});
486
-
487
- expect(result.isErr()).toBe(true);
488
- expect(result.error).toBeInstanceOf(InternalError);
489
- expect(result.error.message).toContain(explodingProvision.id);
490
- expect(result.error.context).toEqual({
491
- provisionId: explodingProvision.id,
492
- });
493
- });
494
-
495
- test('prefers explicit provision overrides over cached or created instances', async () => {
496
- const id = nextProvisionId('override');
497
- let createCalls = 0;
498
- const db = provision(id, {
499
- create: () => {
500
- createCalls += 1;
501
- return Result.ok({ source: 'factory' });
502
- },
503
- });
504
- const provisionTrail = trail('provision.override', {
505
- blaze: (_input, ctx) =>
506
- Result.ok({ source: db.from(ctx).source as string }),
507
- input: z.object({}),
508
- output: z.object({ source: z.string() }),
509
- provisions: [db],
510
- });
511
-
512
- const result = await executeTrail(
513
- provisionTrail,
514
- {},
515
- {
516
- provisions: { [id]: { source: 'override' } },
517
- }
518
- );
519
-
520
- expect(result.isOk()).toBe(true);
521
- expect(result.unwrap()).toEqual({ source: 'override' });
522
- expect(createCalls).toBe(0);
523
- });
524
- });
525
- });