@ontrails/core 1.0.0-beta.15 → 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 (348) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +61 -16
  3. package/package.json +9 -8
  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/context.ts +4 -0
  10. package/src/{internal/cross-batch.ts → cross-batch.ts} +3 -11
  11. package/src/detours.ts +8 -0
  12. package/src/draft.ts +4 -0
  13. package/src/error-projection.ts +51 -0
  14. package/src/errors.ts +192 -39
  15. package/src/execute.ts +443 -44
  16. package/src/fire.ts +965 -91
  17. package/src/index.ts +285 -38
  18. package/src/layer-projection.ts +192 -0
  19. package/src/layer.ts +32 -1
  20. package/src/observe.ts +361 -0
  21. package/src/permits.ts +12 -0
  22. package/src/redaction/patterns.ts +6 -3
  23. package/src/resilience.ts +8 -8
  24. package/src/resource-config.ts +477 -44
  25. package/src/resource.ts +21 -1
  26. package/src/result.ts +18 -0
  27. package/src/run.ts +2 -2
  28. package/src/schedule-runtime.ts +689 -0
  29. package/src/schedule.ts +324 -0
  30. package/src/serialization.ts +90 -57
  31. package/src/signal-diagnostics.ts +633 -0
  32. package/src/{internal/signal-ref.ts → signal-ref.ts} +1 -1
  33. package/src/signal.ts +37 -0
  34. package/src/store/accessor-protocol.ts +5 -5
  35. package/src/structured-examples.ts +245 -0
  36. package/src/surface-derivation.ts +91 -0
  37. package/src/surface-filter.ts +7 -13
  38. package/src/topo.ts +386 -6
  39. package/src/tracing.ts +345 -0
  40. package/src/trail.ts +168 -22
  41. package/src/trails/derive-trail.ts +1 -1
  42. package/src/trails/ingest.ts +7 -15
  43. package/src/{internal/trails-db.ts → trails-db.ts} +42 -21
  44. package/src/transport-error-map.ts +146 -28
  45. package/src/types.ts +82 -28
  46. package/src/validate-established-topo.ts +6 -0
  47. package/src/validate-topo.ts +212 -5
  48. package/src/validation.ts +58 -1
  49. package/src/webhook.ts +276 -0
  50. package/src/{internal/zod-wrappers.ts → zod-wrappers.ts} +0 -6
  51. package/.turbo/turbo-build.log +0 -1
  52. package/.turbo/turbo-lint.log +0 -3
  53. package/.turbo/turbo-typecheck.log +0 -1
  54. package/dist/blob-ref.d.ts +0 -20
  55. package/dist/blob-ref.d.ts.map +0 -1
  56. package/dist/blob-ref.js +0 -22
  57. package/dist/blob-ref.js.map +0 -1
  58. package/dist/branded.d.ts +0 -36
  59. package/dist/branded.d.ts.map +0 -1
  60. package/dist/branded.js +0 -89
  61. package/dist/branded.js.map +0 -1
  62. package/dist/collections.d.ts +0 -31
  63. package/dist/collections.d.ts.map +0 -1
  64. package/dist/collections.js +0 -60
  65. package/dist/collections.js.map +0 -1
  66. package/dist/context.d.ts +0 -22
  67. package/dist/context.d.ts.map +0 -1
  68. package/dist/context.js +0 -37
  69. package/dist/context.js.map +0 -1
  70. package/dist/contour.d.ts +0 -81
  71. package/dist/contour.d.ts.map +0 -1
  72. package/dist/contour.js +0 -176
  73. package/dist/contour.js.map +0 -1
  74. package/dist/cross-schema.d.ts +0 -18
  75. package/dist/cross-schema.d.ts.map +0 -1
  76. package/dist/cross-schema.js +0 -29
  77. package/dist/cross-schema.js.map +0 -1
  78. package/dist/derive.d.ts +0 -44
  79. package/dist/derive.d.ts.map +0 -1
  80. package/dist/derive.js +0 -149
  81. package/dist/derive.js.map +0 -1
  82. package/dist/draft.d.ts +0 -28
  83. package/dist/draft.d.ts.map +0 -1
  84. package/dist/draft.js +0 -157
  85. package/dist/draft.js.map +0 -1
  86. package/dist/errors.d.ts +0 -141
  87. package/dist/errors.d.ts.map +0 -1
  88. package/dist/errors.js +0 -185
  89. package/dist/errors.js.map +0 -1
  90. package/dist/execute.d.ts +0 -52
  91. package/dist/execute.d.ts.map +0 -1
  92. package/dist/execute.js +0 -511
  93. package/dist/execute.js.map +0 -1
  94. package/dist/fetch.d.ts +0 -15
  95. package/dist/fetch.d.ts.map +0 -1
  96. package/dist/fetch.js +0 -102
  97. package/dist/fetch.js.map +0 -1
  98. package/dist/fire.d.ts +0 -43
  99. package/dist/fire.d.ts.map +0 -1
  100. package/dist/fire.js +0 -185
  101. package/dist/fire.js.map +0 -1
  102. package/dist/guards.d.ts +0 -17
  103. package/dist/guards.d.ts.map +0 -1
  104. package/dist/guards.js +0 -25
  105. package/dist/guards.js.map +0 -1
  106. package/dist/index.d.ts +0 -59
  107. package/dist/index.d.ts.map +0 -1
  108. package/dist/index.js +0 -61
  109. package/dist/index.js.map +0 -1
  110. package/dist/internal/cross-batch.d.ts +0 -44
  111. package/dist/internal/cross-batch.d.ts.map +0 -1
  112. package/dist/internal/cross-batch.js +0 -58
  113. package/dist/internal/cross-batch.js.map +0 -1
  114. package/dist/internal/fork-ctx.d.ts +0 -41
  115. package/dist/internal/fork-ctx.d.ts.map +0 -1
  116. package/dist/internal/fork-ctx.js +0 -47
  117. package/dist/internal/fork-ctx.js.map +0 -1
  118. package/dist/internal/signal-ref.d.ts +0 -15
  119. package/dist/internal/signal-ref.d.ts.map +0 -1
  120. package/dist/internal/signal-ref.js +0 -41
  121. package/dist/internal/signal-ref.js.map +0 -1
  122. package/dist/internal/topo-saves.d.ts +0 -53
  123. package/dist/internal/topo-saves.d.ts.map +0 -1
  124. package/dist/internal/topo-saves.js +0 -449
  125. package/dist/internal/topo-saves.js.map +0 -1
  126. package/dist/internal/topo-snapshots.d.ts +0 -57
  127. package/dist/internal/topo-snapshots.d.ts.map +0 -1
  128. package/dist/internal/topo-snapshots.js +0 -372
  129. package/dist/internal/topo-snapshots.js.map +0 -1
  130. package/dist/internal/topo-store-read.d.ts +0 -70
  131. package/dist/internal/topo-store-read.d.ts.map +0 -1
  132. package/dist/internal/topo-store-read.js +0 -221
  133. package/dist/internal/topo-store-read.js.map +0 -1
  134. package/dist/internal/topo-store.d.ts +0 -26
  135. package/dist/internal/topo-store.d.ts.map +0 -1
  136. package/dist/internal/topo-store.js +0 -621
  137. package/dist/internal/topo-store.js.map +0 -1
  138. package/dist/internal/tracing.d.ts +0 -98
  139. package/dist/internal/tracing.d.ts.map +0 -1
  140. package/dist/internal/tracing.js +0 -102
  141. package/dist/internal/tracing.js.map +0 -1
  142. package/dist/internal/trails-db.d.ts +0 -16
  143. package/dist/internal/trails-db.d.ts.map +0 -1
  144. package/dist/internal/trails-db.js +0 -118
  145. package/dist/internal/trails-db.js.map +0 -1
  146. package/dist/internal/zod-wrappers.d.ts +0 -40
  147. package/dist/internal/zod-wrappers.d.ts.map +0 -1
  148. package/dist/internal/zod-wrappers.js +0 -59
  149. package/dist/internal/zod-wrappers.js.map +0 -1
  150. package/dist/layer.d.ts +0 -23
  151. package/dist/layer.d.ts.map +0 -1
  152. package/dist/layer.js +0 -21
  153. package/dist/layer.js.map +0 -1
  154. package/dist/path-security.d.ts +0 -28
  155. package/dist/path-security.d.ts.map +0 -1
  156. package/dist/path-security.js +0 -63
  157. package/dist/path-security.js.map +0 -1
  158. package/dist/patterns/bulk.d.ts +0 -15
  159. package/dist/patterns/bulk.d.ts.map +0 -1
  160. package/dist/patterns/bulk.js +0 -14
  161. package/dist/patterns/bulk.js.map +0 -1
  162. package/dist/patterns/change.d.ts +0 -10
  163. package/dist/patterns/change.d.ts.map +0 -1
  164. package/dist/patterns/change.js +0 -10
  165. package/dist/patterns/change.js.map +0 -1
  166. package/dist/patterns/date-range.d.ts +0 -10
  167. package/dist/patterns/date-range.d.ts.map +0 -1
  168. package/dist/patterns/date-range.js +0 -10
  169. package/dist/patterns/date-range.js.map +0 -1
  170. package/dist/patterns/index.d.ts +0 -9
  171. package/dist/patterns/index.d.ts.map +0 -1
  172. package/dist/patterns/index.js +0 -9
  173. package/dist/patterns/index.js.map +0 -1
  174. package/dist/patterns/pagination.d.ts +0 -18
  175. package/dist/patterns/pagination.d.ts.map +0 -1
  176. package/dist/patterns/pagination.js +0 -18
  177. package/dist/patterns/pagination.js.map +0 -1
  178. package/dist/patterns/progress.d.ts +0 -11
  179. package/dist/patterns/progress.d.ts.map +0 -1
  180. package/dist/patterns/progress.js +0 -11
  181. package/dist/patterns/progress.js.map +0 -1
  182. package/dist/patterns/sorting.d.ts +0 -13
  183. package/dist/patterns/sorting.d.ts.map +0 -1
  184. package/dist/patterns/sorting.js +0 -10
  185. package/dist/patterns/sorting.js.map +0 -1
  186. package/dist/patterns/status.d.ts +0 -15
  187. package/dist/patterns/status.d.ts.map +0 -1
  188. package/dist/patterns/status.js +0 -9
  189. package/dist/patterns/status.js.map +0 -1
  190. package/dist/patterns/timestamps.d.ts +0 -10
  191. package/dist/patterns/timestamps.d.ts.map +0 -1
  192. package/dist/patterns/timestamps.js +0 -10
  193. package/dist/patterns/timestamps.js.map +0 -1
  194. package/dist/redaction/index.d.ts +0 -4
  195. package/dist/redaction/index.d.ts.map +0 -1
  196. package/dist/redaction/index.js +0 -3
  197. package/dist/redaction/index.js.map +0 -1
  198. package/dist/redaction/patterns.d.ts +0 -9
  199. package/dist/redaction/patterns.d.ts.map +0 -1
  200. package/dist/redaction/patterns.js +0 -39
  201. package/dist/redaction/patterns.js.map +0 -1
  202. package/dist/redaction/redactor.d.ts +0 -27
  203. package/dist/redaction/redactor.d.ts.map +0 -1
  204. package/dist/redaction/redactor.js +0 -89
  205. package/dist/redaction/redactor.js.map +0 -1
  206. package/dist/resilience.d.ts +0 -34
  207. package/dist/resilience.d.ts.map +0 -1
  208. package/dist/resilience.js +0 -164
  209. package/dist/resilience.js.map +0 -1
  210. package/dist/resource-config.d.ts +0 -22
  211. package/dist/resource-config.d.ts.map +0 -1
  212. package/dist/resource-config.js +0 -210
  213. package/dist/resource-config.js.map +0 -1
  214. package/dist/resource.d.ts +0 -74
  215. package/dist/resource.d.ts.map +0 -1
  216. package/dist/resource.js +0 -61
  217. package/dist/resource.js.map +0 -1
  218. package/dist/result.d.ts +0 -57
  219. package/dist/result.d.ts.map +0 -1
  220. package/dist/result.js +0 -156
  221. package/dist/result.js.map +0 -1
  222. package/dist/run.d.ts +0 -28
  223. package/dist/run.d.ts.map +0 -1
  224. package/dist/run.js +0 -35
  225. package/dist/run.js.map +0 -1
  226. package/dist/serialization.d.ts +0 -27
  227. package/dist/serialization.d.ts.map +0 -1
  228. package/dist/serialization.js +0 -154
  229. package/dist/serialization.js.map +0 -1
  230. package/dist/signal.d.ts +0 -33
  231. package/dist/signal.d.ts.map +0 -1
  232. package/dist/signal.js +0 -17
  233. package/dist/signal.js.map +0 -1
  234. package/dist/store/accessor-protocol.d.ts +0 -51
  235. package/dist/store/accessor-protocol.d.ts.map +0 -1
  236. package/dist/store/accessor-protocol.js +0 -2
  237. package/dist/store/accessor-protocol.js.map +0 -1
  238. package/dist/store/index.d.ts +0 -2
  239. package/dist/store/index.d.ts.map +0 -1
  240. package/dist/store/index.js +0 -2
  241. package/dist/store/index.js.map +0 -1
  242. package/dist/surface-filter.d.ts +0 -13
  243. package/dist/surface-filter.d.ts.map +0 -1
  244. package/dist/surface-filter.js +0 -76
  245. package/dist/surface-filter.js.map +0 -1
  246. package/dist/topo-store.d.ts +0 -62
  247. package/dist/topo-store.d.ts.map +0 -1
  248. package/dist/topo-store.js +0 -319
  249. package/dist/topo-store.js.map +0 -1
  250. package/dist/topo.d.ts +0 -39
  251. package/dist/topo.d.ts.map +0 -1
  252. package/dist/topo.js +0 -241
  253. package/dist/topo.js.map +0 -1
  254. package/dist/trail.d.ts +0 -163
  255. package/dist/trail.d.ts.map +0 -1
  256. package/dist/trail.js +0 -75
  257. package/dist/trail.js.map +0 -1
  258. package/dist/trails/derive-trail.d.ts +0 -66
  259. package/dist/trails/derive-trail.d.ts.map +0 -1
  260. package/dist/trails/derive-trail.js +0 -378
  261. package/dist/trails/derive-trail.js.map +0 -1
  262. package/dist/trails/index.d.ts +0 -5
  263. package/dist/trails/index.d.ts.map +0 -1
  264. package/dist/trails/index.js +0 -3
  265. package/dist/trails/index.js.map +0 -1
  266. package/dist/trails/ingest.d.ts +0 -24
  267. package/dist/trails/ingest.d.ts.map +0 -1
  268. package/dist/trails/ingest.js +0 -63
  269. package/dist/trails/ingest.js.map +0 -1
  270. package/dist/transport-error-map.d.ts +0 -106
  271. package/dist/transport-error-map.d.ts.map +0 -1
  272. package/dist/transport-error-map.js +0 -24
  273. package/dist/transport-error-map.js.map +0 -1
  274. package/dist/type-checks.test-d.d.ts +0 -71
  275. package/dist/type-checks.test-d.d.ts.map +0 -1
  276. package/dist/type-checks.test-d.js +0 -12
  277. package/dist/type-checks.test-d.js.map +0 -1
  278. package/dist/type-utils.d.ts +0 -33
  279. package/dist/type-utils.d.ts.map +0 -1
  280. package/dist/type-utils.js +0 -12
  281. package/dist/type-utils.js.map +0 -1
  282. package/dist/types.d.ts +0 -177
  283. package/dist/types.d.ts.map +0 -1
  284. package/dist/types.js +0 -3
  285. package/dist/types.js.map +0 -1
  286. package/dist/validate-established-topo.d.ts +0 -76
  287. package/dist/validate-established-topo.d.ts.map +0 -1
  288. package/dist/validate-established-topo.js +0 -43
  289. package/dist/validate-established-topo.js.map +0 -1
  290. package/dist/validate-topo.d.ts +0 -24
  291. package/dist/validate-topo.d.ts.map +0 -1
  292. package/dist/validate-topo.js +0 -193
  293. package/dist/validate-topo.js.map +0 -1
  294. package/dist/validation.d.ts +0 -27
  295. package/dist/validation.d.ts.map +0 -1
  296. package/dist/validation.js +0 -165
  297. package/dist/validation.js.map +0 -1
  298. package/dist/workspace.d.ts +0 -25
  299. package/dist/workspace.d.ts.map +0 -1
  300. package/dist/workspace.js +0 -57
  301. package/dist/workspace.js.map +0 -1
  302. package/src/__tests__/blob-ref.test.ts +0 -103
  303. package/src/__tests__/branded.test.ts +0 -148
  304. package/src/__tests__/collections.test.ts +0 -126
  305. package/src/__tests__/context.test.ts +0 -77
  306. package/src/__tests__/contour.test.ts +0 -263
  307. package/src/__tests__/derive-trail.test.ts +0 -668
  308. package/src/__tests__/derive.test.ts +0 -260
  309. package/src/__tests__/errors.test.ts +0 -427
  310. package/src/__tests__/execute.test.ts +0 -1563
  311. package/src/__tests__/fetch.test.ts +0 -217
  312. package/src/__tests__/fire.test.ts +0 -1056
  313. package/src/__tests__/fork-ctx.test.ts +0 -83
  314. package/src/__tests__/guards.test.ts +0 -102
  315. package/src/__tests__/ingest.test.ts +0 -191
  316. package/src/__tests__/layer.test.ts +0 -125
  317. package/src/__tests__/path-security.test.ts +0 -114
  318. package/src/__tests__/patterns.test.ts +0 -273
  319. package/src/__tests__/redaction.test.ts +0 -244
  320. package/src/__tests__/resilience.test.ts +0 -246
  321. package/src/__tests__/resource.test.ts +0 -204
  322. package/src/__tests__/result.test.ts +0 -155
  323. package/src/__tests__/run.test.ts +0 -181
  324. package/src/__tests__/serialization.test.ts +0 -401
  325. package/src/__tests__/service-config.test.ts +0 -224
  326. package/src/__tests__/signal.test.ts +0 -82
  327. package/src/__tests__/surface-filter.test.ts +0 -196
  328. package/src/__tests__/topo-store-read.test.ts +0 -256
  329. package/src/__tests__/topo-store.test.ts +0 -869
  330. package/src/__tests__/topo.test.ts +0 -573
  331. package/src/__tests__/trail-permit.test.ts +0 -60
  332. package/src/__tests__/trail.test.ts +0 -426
  333. package/src/__tests__/trails-db.test.ts +0 -198
  334. package/src/__tests__/transport-error-map.test.ts +0 -82
  335. package/src/__tests__/type-utils.test.ts +0 -118
  336. package/src/__tests__/validate-topo.test.ts +0 -611
  337. package/src/__tests__/validation.test.ts +0 -336
  338. package/src/__tests__/workspace.test.ts +0 -183
  339. package/src/__tests__/zod-wrappers.test.ts +0 -62
  340. package/src/internal/topo-snapshots.ts +0 -517
  341. package/src/internal/topo-store-read.ts +0 -481
  342. package/src/internal/topo-store.ts +0 -1192
  343. package/src/internal/tracing.ts +0 -187
  344. package/src/topo-store.ts +0 -510
  345. package/src/type-checks.test-d.ts +0 -106
  346. package/tsconfig.json +0 -9
  347. package/tsconfig.tests.json +0 -10
  348. package/tsconfig.tsbuildinfo +0 -1
@@ -1,181 +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 { run } from '../run';
7
- import { InternalError, NotFoundError, ValidationError } from '../errors';
8
- import type { Layer } from '../layer';
9
- import { Result } from '../result';
10
- import { resource } from '../resource';
11
- import { topo } from '../topo';
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 throwingTrail = trail('throws', {
26
- blaze: () => {
27
- throw new Error('kaboom');
28
- },
29
- input: z.object({}),
30
- });
31
-
32
- const testTopo = topo('test', { echoTrail, throwingTrail });
33
-
34
- // ---------------------------------------------------------------------------
35
- // Tests
36
- // ---------------------------------------------------------------------------
37
-
38
- describe('run', () => {
39
- describe('happy path', () => {
40
- test('executes by ID and returns Result.ok with expected value', async () => {
41
- const result = await run(testTopo, 'echo', { value: 'hello' });
42
-
43
- expect(result.isOk()).toBe(true);
44
- expect(result.unwrap()).toEqual({ value: 'hello' });
45
- });
46
-
47
- test('passes resource overrides through to executeTrail', async () => {
48
- const id = `run.resource.${Bun.randomUUIDv7()}`;
49
- const db = resource(id, {
50
- create: () => Result.ok({ source: 'factory' }),
51
- });
52
- const searchTrail = trail('search', {
53
- blaze: (_input, ctx) => Result.ok({ source: db.from(ctx).source }),
54
- input: z.object({}),
55
- output: z.object({ source: z.string() }),
56
- resources: [db],
57
- });
58
- const searchTopo = topo('resource-test', { searchTrail });
59
-
60
- const result = await run(
61
- searchTopo,
62
- 'search',
63
- {},
64
- {
65
- resources: { [id]: { source: 'override' } },
66
- }
67
- );
68
-
69
- expect(result.isOk()).toBe(true);
70
- expect(result.unwrap()).toEqual({ source: 'override' });
71
- });
72
- });
73
-
74
- describe('not found', () => {
75
- test('returns NotFoundError for unknown trail ID', async () => {
76
- const result = await run(testTopo, 'nonexistent', {});
77
-
78
- expect(result.isErr()).toBe(true);
79
- expect(result.error).toBeInstanceOf(NotFoundError);
80
- expect(result.error.message).toContain('nonexistent');
81
- expect(result.error.message).toContain('test');
82
- });
83
- });
84
-
85
- describe('validation', () => {
86
- test('returns ValidationError for invalid input', async () => {
87
- const result = await run(testTopo, 'echo', { value: 42 });
88
-
89
- expect(result.isErr()).toBe(true);
90
- expect(result.error).toBeInstanceOf(ValidationError);
91
- });
92
- });
93
-
94
- describe('layers', () => {
95
- test('layer composition works through run', async () => {
96
- const log: string[] = [];
97
- const layer: Layer = {
98
- name: 'test-layer',
99
- wrap(_trail, impl) {
100
- return async (input, ctx) => {
101
- log.push('before');
102
- const r = await impl(input, ctx);
103
- log.push('after');
104
- return r;
105
- };
106
- },
107
- };
108
-
109
- const result = await run(
110
- testTopo,
111
- 'echo',
112
- { value: 'x' },
113
- { layers: [layer] }
114
- );
115
-
116
- expect(result.isOk()).toBe(true);
117
- expect(log).toEqual(['before', 'after']);
118
- });
119
- });
120
-
121
- describe('context', () => {
122
- test('context overrides work through run', async () => {
123
- let capturedCtx: TrailContext | undefined;
124
- const ctxTrail = trail('ctx-run-test', {
125
- blaze: (_input, ctx) => {
126
- capturedCtx = ctx;
127
- return Result.ok(null);
128
- },
129
- input: z.object({}),
130
- });
131
-
132
- const ctxTopo = topo('ctx-test', { ctxTrail });
133
- await run(
134
- ctxTopo,
135
- 'ctx-run-test',
136
- {},
137
- { ctx: { requestId: 'override-id' } }
138
- );
139
-
140
- expect(capturedCtx?.requestId).toBe('override-id');
141
- });
142
-
143
- test('createContext factory works through run', async () => {
144
- let capturedCtx: TrailContext | undefined;
145
- const ctxTrail = trail('factory-run-test', {
146
- blaze: (_input, ctx) => {
147
- capturedCtx = ctx;
148
- return Result.ok(null);
149
- },
150
- input: z.object({}),
151
- });
152
-
153
- const ctxTopo = topo('factory-test', { ctxTrail });
154
- const customCtx: TrailContextInit = {
155
- abortSignal: new AbortController().signal,
156
- cwd: '/custom',
157
- requestId: 'factory-id',
158
- };
159
-
160
- await run(
161
- ctxTopo,
162
- 'factory-run-test',
163
- {},
164
- { createContext: () => customCtx }
165
- );
166
-
167
- expect(capturedCtx?.requestId).toBe('factory-id');
168
- expect(capturedCtx?.cwd).toBe('/custom');
169
- });
170
- });
171
-
172
- describe('error handling', () => {
173
- test('never throws — exceptions become InternalError', async () => {
174
- const result = await run(testTopo, 'throws', {});
175
-
176
- expect(result.isErr()).toBe(true);
177
- expect(result.error).toBeInstanceOf(InternalError);
178
- expect(result.error.message).toBe('kaboom');
179
- });
180
- });
181
- });
@@ -1,401 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
-
3
- import {
4
- ValidationError,
5
- AmbiguousError,
6
- AssertionError,
7
- NetworkError,
8
- RateLimitError,
9
- InternalError,
10
- TimeoutError,
11
- NotFoundError,
12
- AlreadyExistsError,
13
- ConflictError,
14
- PermissionError,
15
- AuthError,
16
- CancelledError,
17
- } from '../errors.js';
18
- import {
19
- serializeError,
20
- deserializeError,
21
- safeStringify,
22
- } from '../serialization.js';
23
- import { Result } from '../result.js';
24
- import type { SerializedError } from '../serialization.js';
25
-
26
- // ---------------------------------------------------------------------------
27
- // serializeError
28
- // ---------------------------------------------------------------------------
29
-
30
- describe('serializeError', () => {
31
- test('serializes a plain Error', () => {
32
- const err = new Error('boom');
33
- const serialized = serializeError(err);
34
- expect(serialized.name).toBe('Error');
35
- expect(serialized.message).toBe('boom');
36
- expect(serialized.stack).toBeDefined();
37
- expect(serialized.category).toBeUndefined();
38
- expect(serialized.retryable).toBeUndefined();
39
- });
40
-
41
- test('serializes a TrailsError with category', () => {
42
- const err = new NetworkError('disconnected');
43
- const serialized = serializeError(err);
44
- expect(serialized.name).toBe('NetworkError');
45
- expect(serialized.message).toBe('disconnected');
46
- expect(serialized.category).toBe('network');
47
- expect(serialized.retryable).toBe(true);
48
- });
49
-
50
- test('serializes context', () => {
51
- const err = new ValidationError('bad input', {
52
- context: { field: 'email' },
53
- });
54
- const serialized = serializeError(err);
55
- expect(serialized.context).toEqual({ field: 'email' });
56
- });
57
-
58
- test('serializes RateLimitError.retryAfter', () => {
59
- const err = new RateLimitError('slow down', { retryAfter: 30 });
60
- const serialized = serializeError(err);
61
- expect(serialized.retryAfter).toBe(30);
62
- });
63
-
64
- test('retryAfter is undefined for non-RateLimitError', () => {
65
- const err = new TimeoutError('timed out');
66
- const serialized = serializeError(err);
67
- expect(serialized.retryAfter).toBeUndefined();
68
- });
69
- });
70
-
71
- // ---------------------------------------------------------------------------
72
- // deserializeError
73
- // ---------------------------------------------------------------------------
74
-
75
- describe('deserializeError', () => {
76
- test('reconstructs a validation error', () => {
77
- const data: SerializedError = {
78
- category: 'validation',
79
- message: 'bad input',
80
- name: 'ValidationError',
81
- retryable: false,
82
- };
83
- const err = deserializeError(data);
84
- expect(err).toBeInstanceOf(ValidationError);
85
- expect(err.message).toBe('bad input');
86
- expect(err.category).toBe('validation');
87
- });
88
-
89
- test('reconstructs a rate limit error with retryAfter', () => {
90
- const data: SerializedError = {
91
- category: 'rate_limit',
92
- message: 'slow down',
93
- name: 'RateLimitError',
94
- retryAfter: 60,
95
- retryable: true,
96
- };
97
- const err = deserializeError(data);
98
- expect(err).toBeInstanceOf(RateLimitError);
99
- expect((err as RateLimitError).retryAfter).toBe(60);
100
- });
101
-
102
- test('restores stack trace', () => {
103
- const data: SerializedError = {
104
- category: 'internal',
105
- message: 'oops',
106
- name: 'InternalError',
107
- stack: 'Error: oops\n at test.ts:1:1',
108
- };
109
- const err = deserializeError(data);
110
- expect(err.stack).toBe('Error: oops\n at test.ts:1:1');
111
- });
112
-
113
- test('restores context', () => {
114
- const data: SerializedError = {
115
- category: 'auth',
116
- context: { endpoint: '/api/secret' },
117
- message: 'unauthorized',
118
- name: 'AuthError',
119
- };
120
- const err = deserializeError(data);
121
- expect(err.context).toEqual({ endpoint: '/api/secret' });
122
- });
123
-
124
- test('defaults to InternalError when category is missing', () => {
125
- const data: SerializedError = {
126
- message: 'unknown',
127
- name: 'SomeError',
128
- };
129
- const err = deserializeError(data);
130
- expect(err).toBeInstanceOf(InternalError);
131
- expect(err.category).toBe('internal');
132
- });
133
-
134
- test('round-trips through serialize/deserialize', () => {
135
- const original = new NotFoundError('missing resource', {
136
- context: { id: 'abc' },
137
- });
138
- const serialized = serializeError(original);
139
- const restored = deserializeError(serialized);
140
- expect(restored).toBeInstanceOf(NotFoundError);
141
- expect(restored.message).toBe('missing resource');
142
- expect(restored.context).toEqual({ id: 'abc' });
143
- expect(restored.category).toBe('not_found');
144
- });
145
-
146
- test('handles all error categories', () => {
147
- const categories = [
148
- 'validation',
149
- 'not_found',
150
- 'conflict',
151
- 'permission',
152
- 'timeout',
153
- 'rate_limit',
154
- 'network',
155
- 'internal',
156
- 'auth',
157
- 'cancelled',
158
- ] as const;
159
-
160
- for (const category of categories) {
161
- const data: SerializedError = {
162
- category,
163
- message: 'test',
164
- name: 'Test',
165
- };
166
- const err = deserializeError(data);
167
- expect(err.category).toBe(category);
168
- }
169
- });
170
-
171
- describe('round-trips all subclasses by name', () => {
172
- const subclasses = [
173
- { Ctor: ValidationError, category: 'validation' },
174
- { Ctor: AmbiguousError, category: 'validation' },
175
- { Ctor: AssertionError, category: 'internal' },
176
- { Ctor: NotFoundError, category: 'not_found' },
177
- { Ctor: AlreadyExistsError, category: 'conflict' },
178
- { Ctor: ConflictError, category: 'conflict' },
179
- { Ctor: PermissionError, category: 'permission' },
180
- { Ctor: TimeoutError, category: 'timeout' },
181
- { Ctor: NetworkError, category: 'network' },
182
- { Ctor: InternalError, category: 'internal' },
183
- { Ctor: AuthError, category: 'auth' },
184
- { Ctor: CancelledError, category: 'cancelled' },
185
- ] as const;
186
-
187
- test.each(subclasses)(
188
- '$Ctor.name round-trips with correct identity',
189
- ({ Ctor, category }) => {
190
- const original = new Ctor(`test ${Ctor.name}`, {
191
- context: { key: 'value' },
192
- });
193
- const serialized = serializeError(original);
194
- const restored = deserializeError(serialized);
195
-
196
- expect(restored).toBeInstanceOf(Ctor);
197
- expect(restored.constructor.name).toBe(Ctor.name);
198
- expect(restored.name).toBe(Ctor.name);
199
- expect(restored.category).toBe(category);
200
- expect(restored.message).toBe(`test ${Ctor.name}`);
201
- expect(restored.context).toEqual({ key: 'value' });
202
- }
203
- );
204
-
205
- test('RateLimitError round-trips with retryAfter', () => {
206
- const original = new RateLimitError('slow down', {
207
- context: { endpoint: '/api' },
208
- retryAfter: 42,
209
- });
210
- const serialized = serializeError(original);
211
- const restored = deserializeError(serialized);
212
-
213
- expect(restored).toBeInstanceOf(RateLimitError);
214
- expect(restored.constructor.name).toBe('RateLimitError');
215
- expect((restored as RateLimitError).retryAfter).toBe(42);
216
- expect(restored.context).toEqual({ endpoint: '/api' });
217
- });
218
-
219
- test('falls back to category when name is unknown', () => {
220
- const data: SerializedError = {
221
- category: 'conflict',
222
- message: 'custom error',
223
- name: 'CustomConflictError',
224
- };
225
- const err = deserializeError(data);
226
- expect(err).toBeInstanceOf(ConflictError);
227
- expect(err.category).toBe('conflict');
228
- });
229
- });
230
- });
231
-
232
- // ---------------------------------------------------------------------------
233
- // Result.fromJson
234
- // ---------------------------------------------------------------------------
235
-
236
- describe('Result.fromJson (safeParse)', () => {
237
- test('parses valid JSON', () => {
238
- const result = Result.fromJson('{"key": "value"}');
239
- expect(result.isOk()).toBe(true);
240
- expect(result.unwrap()).toEqual({ key: 'value' });
241
- });
242
-
243
- test('parses JSON arrays', () => {
244
- const result = Result.fromJson('[1, 2, 3]');
245
- expect(result.isOk()).toBe(true);
246
- expect(result.unwrap()).toEqual([1, 2, 3]);
247
- });
248
-
249
- test('parses JSON primitives', () => {
250
- expect(Result.fromJson('"hello"').unwrap()).toBe('hello');
251
- expect(Result.fromJson('42').unwrap()).toBe(42);
252
- expect(Result.fromJson('true').unwrap()).toBe(true);
253
- expect(Result.fromJson('null').unwrap()).toBe(null);
254
- });
255
-
256
- test('returns ValidationError for invalid JSON', () => {
257
- const result = Result.fromJson('not json');
258
- expect(result.isErr()).toBe(true);
259
- const err = result as unknown as { error: ValidationError };
260
- expect(err.error).toBeInstanceOf(ValidationError);
261
- expect(err.error.message).toBe('Invalid JSON');
262
- });
263
-
264
- test('includes truncated input in context', () => {
265
- const result = Result.fromJson('{bad}');
266
- expect(result.isErr()).toBe(true);
267
- const err = result as unknown as { error: ValidationError };
268
- expect(err.error.context).toBeDefined();
269
- expect(err.error.context?.['input']).toBe('{bad}');
270
- });
271
- });
272
-
273
- // ---------------------------------------------------------------------------
274
- // Result.toJson
275
- // ---------------------------------------------------------------------------
276
-
277
- describe('Result.toJson (safeStringify)', () => {
278
- test('stringifies objects', () => {
279
- const result = Result.toJson({ key: 'value' });
280
- expect(result.isOk()).toBe(true);
281
- expect(result.unwrap()).toBe('{"key":"value"}');
282
- });
283
-
284
- test('stringifies arrays', () => {
285
- const result = Result.toJson([1, 2, 3]);
286
- expect(result.isOk()).toBe(true);
287
- expect(result.unwrap()).toBe('[1,2,3]');
288
- });
289
-
290
- test('stringifies primitives', () => {
291
- expect(Result.toJson('hello').unwrap()).toBe('"hello"');
292
- expect(Result.toJson(42).unwrap()).toBe('42');
293
- expect(Result.toJson(true).unwrap()).toBe('true');
294
- expect(Result.toJson(null).unwrap()).toBe('null');
295
- });
296
-
297
- test('handles circular references gracefully', () => {
298
- const obj: Record<string, unknown> = { a: 1 };
299
- obj['self'] = obj;
300
- const result = Result.toJson(obj);
301
- expect(result.isOk()).toBe(true);
302
- const ok = result as unknown as { value: string };
303
- const parsed = JSON.parse(ok.value) as Record<string, unknown>;
304
- expect(parsed['a']).toBe(1);
305
- expect(parsed['self']).toBe('[Circular]');
306
- });
307
-
308
- test('serializes shared references in a DAG without marking as circular', () => {
309
- const shared = { x: 1 };
310
- const obj = { a: shared, b: shared };
311
- const result = Result.toJson(obj);
312
- expect(result.isOk()).toBe(true);
313
- const parsed = JSON.parse(result.unwrap()) as Record<string, unknown>;
314
- expect(parsed['a']).toEqual({ x: 1 });
315
- expect(parsed['b']).toEqual({ x: 1 });
316
- });
317
-
318
- test('detects deep circular references', () => {
319
- const inner: Record<string, unknown> = { value: 'deep' };
320
- const obj: Record<string, unknown> = { child: { nested: inner } };
321
- inner['back'] = obj;
322
- const result = Result.toJson(obj);
323
- expect(result.isOk()).toBe(true);
324
- const parsed = JSON.parse(result.unwrap()) as Record<string, unknown>;
325
- const child = parsed['child'] as Record<string, unknown>;
326
- const nested = child['nested'] as Record<string, unknown>;
327
- expect(nested['value']).toBe('deep');
328
- expect(nested['back']).toBe('[Circular]');
329
- });
330
-
331
- test('handles shared ref used in sibling subtrees of a DAG', () => {
332
- const shared = { id: 42 };
333
- const obj = {
334
- left: { extra: 'l', ref: shared },
335
- right: { extra: 'r', ref: shared },
336
- };
337
- const result = Result.toJson(obj);
338
- expect(result.isOk()).toBe(true);
339
- const parsed = JSON.parse(result.unwrap()) as Record<
340
- string,
341
- Record<string, unknown>
342
- >;
343
- expect(parsed['left']?.['ref']).toEqual({ id: 42 });
344
- expect(parsed['right']?.['ref']).toEqual({ id: 42 });
345
- });
346
- });
347
-
348
- // ---------------------------------------------------------------------------
349
- // safeStringify (shared DAG / circular detection)
350
- // ---------------------------------------------------------------------------
351
-
352
- describe('safeStringify', () => {
353
- test('serializes shared references in a DAG without marking as circular', () => {
354
- const shared = { x: 1 };
355
- const obj = { a: shared, b: shared };
356
- const result = safeStringify(obj);
357
- expect(result.isOk()).toBe(true);
358
- const parsed = JSON.parse(result.unwrap()) as Record<string, unknown>;
359
- expect(parsed['a']).toEqual({ x: 1 });
360
- expect(parsed['b']).toEqual({ x: 1 });
361
- });
362
-
363
- test('detects true circular references', () => {
364
- const obj: Record<string, unknown> = { a: 1 };
365
- obj['self'] = obj;
366
- const result = safeStringify(obj);
367
- expect(result.isOk()).toBe(true);
368
- const parsed = JSON.parse(result.unwrap()) as Record<string, unknown>;
369
- expect(parsed['a']).toBe(1);
370
- expect(parsed['self']).toBe('[Circular]');
371
- });
372
-
373
- test('detects deep circular references', () => {
374
- const inner: Record<string, unknown> = { value: 'deep' };
375
- const obj: Record<string, unknown> = { child: { nested: inner } };
376
- inner['back'] = obj;
377
- const result = safeStringify(obj);
378
- expect(result.isOk()).toBe(true);
379
- const parsed = JSON.parse(result.unwrap()) as Record<string, unknown>;
380
- const child = parsed['child'] as Record<string, unknown>;
381
- const nested = child['nested'] as Record<string, unknown>;
382
- expect(nested['value']).toBe('deep');
383
- expect(nested['back']).toBe('[Circular]');
384
- });
385
-
386
- test('handles shared ref used in sibling subtrees of a DAG', () => {
387
- const shared = { id: 42 };
388
- const obj = {
389
- left: { extra: 'l', ref: shared },
390
- right: { extra: 'r', ref: shared },
391
- };
392
- const result = safeStringify(obj);
393
- expect(result.isOk()).toBe(true);
394
- const parsed = JSON.parse(result.unwrap()) as Record<
395
- string,
396
- Record<string, unknown>
397
- >;
398
- expect(parsed['left']?.['ref']).toEqual({ id: 42 });
399
- expect(parsed['right']?.['ref']).toEqual({ id: 42 });
400
- });
401
- });