@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,426 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
-
3
- import { z } from 'zod';
4
-
5
- import { contour } from '../contour';
6
- import { createTrailContext } from '../context';
7
- import { ConflictError } from '../errors';
8
- import { Result } from '../result';
9
- import { resource } from '../resource';
10
- import { signal } from '../signal';
11
- import { trail } from '../trail';
12
- import type { TrailContext } from '../types';
13
-
14
- const stubCtx: TrailContext = createTrailContext({
15
- abortSignal: AbortSignal.timeout(5000),
16
- requestId: 'test-123',
17
- });
18
-
19
- const dbResource = resource('db.main', {
20
- create: () =>
21
- Result.ok({
22
- query(sql: string) {
23
- return sql.length;
24
- },
25
- }),
26
- description: 'Primary database resource',
27
- });
28
-
29
- const userContour = contour(
30
- 'user',
31
- {
32
- id: z.string().uuid(),
33
- name: z.string(),
34
- },
35
- { identity: 'id' }
36
- );
37
-
38
- describe('trail()', () => {
39
- const inputSchema = z.object({ name: z.string() });
40
- const outputSchema = z.object({ greeting: z.string() });
41
-
42
- const greet = trail('greet', {
43
- blaze: (input) => Result.ok({ greeting: `Hello, ${input.name}!` }),
44
- description: 'Greet someone',
45
- input: inputSchema,
46
- output: outputSchema,
47
- });
48
-
49
- describe('basics', () => {
50
- test('returns correct id', () => {
51
- expect(greet.id).toBe('greet');
52
- });
53
-
54
- test("returns kind 'trail'", () => {
55
- expect(greet.kind).toBe('trail');
56
- });
57
-
58
- test('preserves input schema', () => {
59
- const parsed = greet.input.safeParse({ name: 'World' });
60
- expect(parsed.success).toBe(true);
61
-
62
- const bad = greet.input.safeParse({ name: 42 });
63
- expect(bad.success).toBe(false);
64
- });
65
-
66
- test('output schema is optional', () => {
67
- const minimal = trail('noop', {
68
- blaze: () => Result.ok(),
69
- input: z.object({}),
70
- });
71
- expect(minimal.output).toBeUndefined();
72
- });
73
-
74
- test('implementation is callable', async () => {
75
- const result = await greet.blaze({ name: 'World' }, stubCtx);
76
- expect(result.isOk()).toBe(true);
77
- expect(result.unwrap()).toEqual({ greeting: 'Hello, World!' });
78
- });
79
- });
80
-
81
- describe('meta', () => {
82
- test('examples are stored', () => {
83
- const withExamples = trail('echo', {
84
- blaze: (input) => Result.ok({ text: input.text }),
85
- examples: [
86
- { error: 'ValidationError', input: { text: '' }, name: 'error-case' },
87
- { expected: { text: 'hi' }, input: { text: 'hi' }, name: 'basic' },
88
- ],
89
- input: z.object({ text: z.string() }),
90
- });
91
- expect(withExamples.examples).toHaveLength(2);
92
- const first = withExamples.examples?.[0];
93
- expect(first?.name).toBe('error-case');
94
- const second = withExamples.examples?.[1];
95
- expect(second?.name).toBe('basic');
96
- });
97
-
98
- test('meta is stored', () => {
99
- const withMeta = trail('tagged', {
100
- blaze: () => Result.ok(),
101
- input: z.object({}),
102
- meta: { domain: 'billing', tier: 1 },
103
- });
104
- expect(withMeta.meta).toEqual({ domain: 'billing', tier: 1 });
105
- });
106
-
107
- test('pattern is stored when declared', () => {
108
- const withPattern = trail('feature.enable', {
109
- blaze: () => Result.ok({ enabled: true }),
110
- input: z.object({ id: z.string() }),
111
- pattern: 'toggle',
112
- });
113
-
114
- expect(withPattern.pattern).toBe('toggle');
115
- });
116
-
117
- test('detours are stored', () => {
118
- const withDetours = trail('orchestrator', {
119
- blaze: () => Result.ok(),
120
- /* oxlint-disable-next-line require-await -- test stub */
121
- detours: [{ on: ConflictError, recover: async () => Result.ok() }],
122
- input: z.object({}),
123
- });
124
- expect(withDetours.detours).toHaveLength(1);
125
- expect(withDetours.detours[0]?.on).toBe(ConflictError);
126
- });
127
-
128
- test('detours default to empty frozen array when omitted', () => {
129
- const noDetours = trail('bare', {
130
- blaze: () => Result.ok(),
131
- input: z.object({}),
132
- });
133
- expect(noDetours.detours).toEqual([]);
134
- expect(Object.isFrozen(noDetours.detours)).toBe(true);
135
- });
136
- });
137
-
138
- describe('crosses', () => {
139
- test('defaults to empty frozen array when omitted', () => {
140
- const minimal = trail('bare', {
141
- blaze: () => Result.ok(),
142
- input: z.object({}),
143
- });
144
- expect(minimal.crosses).toEqual([]);
145
- expect(Object.isFrozen(minimal.crosses)).toBe(true);
146
- });
147
-
148
- test('preserves crosses array', () => {
149
- const withCrosses = trail('composed', {
150
- blaze: () => Result.ok(),
151
- crosses: ['authenticate', 'validate-session'],
152
- input: z.object({}),
153
- });
154
- expect(withCrosses.crosses).toEqual(['authenticate', 'validate-session']);
155
- });
156
-
157
- test('crosses array is frozen', () => {
158
- const withCrosses = trail('composed', {
159
- blaze: () => Result.ok(),
160
- crosses: ['authenticate'],
161
- input: z.object({}),
162
- });
163
- expect(Object.isFrozen(withCrosses.crosses)).toBe(true);
164
- });
165
-
166
- test('trail object in crosses is normalized to its id', () => {
167
- const target = trail('target.trail', {
168
- blaze: () => Result.ok(),
169
- input: z.object({}),
170
- });
171
- const composed = trail('composed', {
172
- blaze: () => Result.ok(),
173
- crosses: [target],
174
- input: z.object({}),
175
- });
176
- expect(composed.crosses).toEqual(['target.trail']);
177
- });
178
-
179
- test('mixed string and trail object in crosses normalizes correctly', () => {
180
- const target = trail('target.trail', {
181
- blaze: () => Result.ok(),
182
- input: z.object({}),
183
- });
184
- const composed = trail('composed', {
185
- blaze: () => Result.ok(),
186
- crosses: ['string-id', target],
187
- input: z.object({}),
188
- });
189
- expect(composed.crosses).toEqual(['string-id', 'target.trail']);
190
- });
191
-
192
- test('crossInput is stored on the trail', () => {
193
- const crossInputSchema = z.object({ forkedFrom: z.string().optional() });
194
- const t = trail('gist.create', {
195
- blaze: () => Result.ok(),
196
- crossInput: crossInputSchema,
197
- input: z.object({ content: z.string() }),
198
- });
199
- expect(t.crossInput).toBe(crossInputSchema);
200
- });
201
-
202
- test('crossInput is undefined when omitted', () => {
203
- const t = trail('bare', {
204
- blaze: () => Result.ok(),
205
- input: z.object({}),
206
- });
207
- expect(t.crossInput).toBeUndefined();
208
- });
209
- });
210
-
211
- describe('contours', () => {
212
- test('defaults to empty frozen array when omitted', () => {
213
- const minimal = trail('bare', {
214
- blaze: () => Result.ok(),
215
- input: z.object({}),
216
- });
217
- expect(minimal.contours).toEqual([]);
218
- expect(Object.isFrozen(minimal.contours)).toBe(true);
219
- });
220
-
221
- test('preserves declared contour objects', () => {
222
- const withContours = trail('user.create', {
223
- blaze: () => Result.ok(),
224
- contours: [userContour],
225
- input: z.object({}),
226
- });
227
- expect(withContours.contours).toEqual([userContour]);
228
- expect(withContours.contours[0]).toBe(userContour);
229
- });
230
-
231
- test('contours array is frozen', () => {
232
- const withContours = trail('user.create', {
233
- blaze: () => Result.ok(),
234
- contours: [userContour],
235
- input: z.object({}),
236
- });
237
- expect(Object.isFrozen(withContours.contours)).toBe(true);
238
- });
239
- });
240
-
241
- describe('resources', () => {
242
- test('defaults to empty frozen array when omitted', () => {
243
- const minimal = trail('bare', {
244
- blaze: () => Result.ok(),
245
- input: z.object({}),
246
- });
247
- expect(minimal.resources).toEqual([]);
248
- expect(Object.isFrozen(minimal.resources)).toBe(true);
249
- });
250
-
251
- test('preserves declared resource objects', () => {
252
- const withResources = trail('search', {
253
- blaze: () => Result.ok(),
254
- input: z.object({}),
255
- resources: [dbResource],
256
- });
257
- expect(withResources.resources).toEqual([dbResource]);
258
- expect(withResources.resources[0]).toBe(dbResource);
259
- });
260
-
261
- test('resources array is frozen', () => {
262
- const withResources = trail('search', {
263
- blaze: () => Result.ok(),
264
- input: z.object({}),
265
- resources: [dbResource],
266
- });
267
- expect(Object.isFrozen(withResources.resources)).toBe(true);
268
- });
269
- });
270
-
271
- describe('intent and idempotent', () => {
272
- test('intent defaults to write', () => {
273
- const minimal = trail('bare', {
274
- blaze: () => Result.ok(),
275
- input: z.object({}),
276
- });
277
- expect(minimal.intent).toBe('write');
278
- expect(minimal.idempotent).toBeUndefined();
279
- });
280
-
281
- test('intent is preserved when set', () => {
282
- const readTrail = trail('reader', {
283
- blaze: () => Result.ok(),
284
- input: z.object({}),
285
- intent: 'read',
286
- });
287
- expect(readTrail.intent).toBe('read');
288
-
289
- const destroyTrail = trail('destroyer', {
290
- blaze: () => Result.ok(),
291
- input: z.object({}),
292
- intent: 'destroy',
293
- });
294
- expect(destroyTrail.intent).toBe('destroy');
295
- });
296
-
297
- test('idempotent is preserved when set', () => {
298
- const t = trail('idempotent', {
299
- blaze: () => Result.ok(),
300
- idempotent: true,
301
- input: z.object({}),
302
- });
303
- expect(t.idempotent).toBe(true);
304
- });
305
-
306
- test('visibility defaults to public', () => {
307
- const minimal = trail('visible', {
308
- blaze: () => Result.ok(),
309
- input: z.object({}),
310
- });
311
- expect(minimal.visibility).toBe('public');
312
- });
313
-
314
- test('visibility is preserved when set', () => {
315
- const t = trail('internal.helper', {
316
- blaze: () => Result.ok(),
317
- input: z.object({}),
318
- visibility: 'internal',
319
- });
320
- expect(t.visibility).toBe('internal');
321
- });
322
- });
323
-
324
- describe('single-object overload', () => {
325
- test('accepts spec with id property', () => {
326
- const t = trail({
327
- blaze: (input: { name: string }, _ctx: TrailContext) =>
328
- Result.ok({ greeting: `Hi, ${input.name}` }),
329
- id: 'entity.show',
330
- input: inputSchema,
331
- });
332
- expect(t.id).toBe('entity.show');
333
- expect(t.kind).toBe('trail');
334
- });
335
-
336
- test('preserves all spec fields', () => {
337
- const t = trail({
338
- blaze: (input: { name: string }, _ctx: TrailContext) =>
339
- Result.ok({ greeting: `Hi, ${input.name}` }),
340
- description: 'A full trail',
341
- examples: [{ input: { name: 'World' }, name: 'test' }],
342
- id: 'full',
343
- input: inputSchema,
344
- intent: 'read',
345
- output: outputSchema,
346
- resources: [dbResource],
347
- });
348
- expect(t.description).toBe('A full trail');
349
- expect(t.intent).toBe('read');
350
- expect(t.examples).toHaveLength(1);
351
- expect(t.resources).toEqual([dbResource]);
352
- });
353
-
354
- test('implementation is callable', async () => {
355
- const t = trail({
356
- blaze: (input: { x: number }) => Result.ok(input.x * 2),
357
- id: 'callable',
358
- input: z.object({ x: z.number() }),
359
- });
360
- const result = await t.blaze({ x: 5 }, stubCtx);
361
- expect(result.isOk()).toBe(true);
362
- expect(result.unwrap()).toBe(10);
363
- });
364
-
365
- test('sync implementations are normalized to an awaitable runtime function', async () => {
366
- const t = trail('normalized', {
367
- blaze: (input: { value: number }) => Result.ok(input.value + 1),
368
- input: z.object({ value: z.number() }),
369
- });
370
-
371
- const promise = t.blaze({ value: 2 }, stubCtx);
372
- expect(promise).toBeInstanceOf(Promise);
373
-
374
- const result = await promise;
375
- expect(result.isOk()).toBe(true);
376
- expect(result.unwrap()).toBe(3);
377
- });
378
- });
379
- });
380
-
381
- describe('trail() fires/on normalization', () => {
382
- const orderPlaced = signal('order.placed', {
383
- payload: z.object({ id: z.string() }),
384
- });
385
- const auditLogged = signal('audit.logged', {
386
- payload: z.object({ actor: z.string() }),
387
- });
388
-
389
- test('Signal value in fires: is normalized to its id', () => {
390
- const t = trail('checkout', {
391
- blaze: () => Result.ok({}),
392
- fires: [orderPlaced],
393
- input: z.object({}),
394
- });
395
- expect(t.fires).toEqual(['order.placed']);
396
- });
397
-
398
- test('Signal value in on: is normalized to its id', () => {
399
- const t = trail('notify', {
400
- blaze: () => Result.ok({}),
401
- input: z.object({}),
402
- on: [orderPlaced],
403
- });
404
- expect(t.on).toEqual(['order.placed']);
405
- });
406
-
407
- test('mixed string + Signal value in fires: is normalized', () => {
408
- const t = trail('checkout', {
409
- blaze: () => Result.ok({}),
410
- fires: ['metric.emitted', orderPlaced, auditLogged],
411
- input: z.object({}),
412
- });
413
- expect(t.fires).toEqual(['metric.emitted', 'order.placed', 'audit.logged']);
414
- });
415
-
416
- test('defaults to empty frozen arrays when omitted', () => {
417
- const minimal = trail('bare', {
418
- blaze: () => Result.ok(),
419
- input: z.object({}),
420
- });
421
- expect(minimal.fires).toEqual([]);
422
- expect(Object.isFrozen(minimal.fires)).toBe(true);
423
- expect(minimal.on).toEqual([]);
424
- expect(Object.isFrozen(minimal.on)).toBe(true);
425
- });
426
- });
@@ -1,198 +0,0 @@
1
- import { afterEach, describe, expect, test } from 'bun:test';
2
- import { existsSync, mkdtempSync, rmSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
-
6
- import {
7
- ensureSubsystemSchema,
8
- openReadTrailsDb,
9
- openWriteTrailsDb,
10
- deriveTrailsDbPath,
11
- } from '../internal/trails-db.js';
12
- import {
13
- createTopoSnapshot,
14
- ensureTopoSnapshotSchema,
15
- listTopoSnapshots,
16
- pinTopoSnapshot,
17
- pruneUnpinnedSnapshots,
18
- } from '../internal/topo-snapshots.js';
19
-
20
- describe('trails db foundation', () => {
21
- let tmpRoot: string | undefined;
22
-
23
- afterEach(() => {
24
- if (tmpRoot) {
25
- rmSync(tmpRoot, { force: true, recursive: true });
26
- tmpRoot = undefined;
27
- }
28
- });
29
-
30
- const makeRoot = (): string => {
31
- tmpRoot = mkdtempSync(join(tmpdir(), 'trails-db-'));
32
- return tmpRoot;
33
- };
34
-
35
- const expectWorkspaceLayout = (rootDir: string): void => {
36
- expect(existsSync(join(rootDir, '.trails', '.gitignore'))).toBe(true);
37
- expect(existsSync(join(rootDir, '.trails', 'config'))).toBe(true);
38
- expect(existsSync(join(rootDir, '.trails', 'dev'))).toBe(true);
39
- expect(existsSync(join(rootDir, '.trails', 'generated'))).toBe(true);
40
- };
41
-
42
- test('deriveTrailsDbPath places the database in .trails/trails.db', () => {
43
- const rootDir = '/tmp/example-app';
44
- expect(deriveTrailsDbPath({ rootDir })).toBe(
45
- '/tmp/example-app/.trails/trails.db'
46
- );
47
- });
48
-
49
- test('openWriteTrailsDb creates the database with WAL and NORMAL defaults', () => {
50
- const rootDir = makeRoot();
51
- const db = openWriteTrailsDb({ rootDir });
52
-
53
- try {
54
- expect(existsSync(deriveTrailsDbPath({ rootDir }))).toBe(true);
55
- expectWorkspaceLayout(rootDir);
56
-
57
- const journal = db
58
- .query<{ journal_mode: string }, []>('PRAGMA journal_mode')
59
- .get();
60
- const synchronous = db
61
- .query<{ synchronous: number }, []>('PRAGMA synchronous')
62
- .get();
63
-
64
- expect(journal?.journal_mode.toLowerCase()).toBe('wal');
65
- expect(synchronous?.synchronous).toBe(1);
66
- } finally {
67
- db.close();
68
- }
69
- });
70
-
71
- test('openReadTrailsDb blocks writes at the SQLite connection level', () => {
72
- const rootDir = makeRoot();
73
- const writer = openWriteTrailsDb({ rootDir });
74
- writer.close();
75
-
76
- const reader = openReadTrailsDb({ rootDir });
77
-
78
- try {
79
- expect(() => reader.run('CREATE TABLE readonly_probe (id TEXT)')).toThrow(
80
- /readonly|read-only/i
81
- );
82
- } finally {
83
- reader.close();
84
- }
85
- });
86
-
87
- test('ensureSubsystemSchema only migrates when the subsystem version changes', () => {
88
- const rootDir = makeRoot();
89
- const db = openWriteTrailsDb({ rootDir });
90
- let calls = 0;
91
-
92
- try {
93
- ensureSubsystemSchema(db, {
94
- migrate: () => {
95
- calls += 1;
96
- db.run(
97
- 'CREATE TABLE IF NOT EXISTS track_records (id TEXT PRIMARY KEY)'
98
- );
99
- },
100
- subsystem: 'track',
101
- version: 1,
102
- });
103
-
104
- ensureSubsystemSchema(db, {
105
- migrate: () => {
106
- calls += 1;
107
- },
108
- subsystem: 'track',
109
- version: 1,
110
- });
111
-
112
- ensureSubsystemSchema(db, {
113
- migrate: (currentVersion) => {
114
- calls += 1;
115
- expect(currentVersion).toBe(1);
116
- db.run('ALTER TABLE track_records ADD COLUMN status TEXT');
117
- },
118
- subsystem: 'track',
119
- version: 2,
120
- });
121
-
122
- expect(calls).toBe(2);
123
- } finally {
124
- db.close();
125
- }
126
- });
127
- });
128
-
129
- describe('topo snapshot primitives', () => {
130
- let tmpRoot: string | undefined;
131
-
132
- afterEach(() => {
133
- if (tmpRoot) {
134
- rmSync(tmpRoot, { force: true, recursive: true });
135
- tmpRoot = undefined;
136
- }
137
- });
138
-
139
- const makeRoot = (): string => {
140
- tmpRoot = mkdtempSync(join(tmpdir(), 'topo-snapshots-'));
141
- return tmpRoot;
142
- };
143
-
144
- const seedHistory = (db: ReturnType<typeof openWriteTrailsDb>) => {
145
- ensureTopoSnapshotSchema(db);
146
-
147
- const pinned = createTopoSnapshot(db, {
148
- createdAt: '2026-04-01T00:00:00.000Z',
149
- gitDirty: false,
150
- gitSha: 'abc123',
151
- resourceCount: 2,
152
- signalCount: 1,
153
- trailCount: 3,
154
- });
155
- const disposable = createTopoSnapshot(db, {
156
- createdAt: '2026-04-02T00:00:00.000Z',
157
- gitDirty: true,
158
- gitSha: 'def456',
159
- resourceCount: 3,
160
- signalCount: 2,
161
- trailCount: 4,
162
- });
163
-
164
- return {
165
- disposable,
166
- pinned,
167
- pinnedSnapshot: pinTopoSnapshot(db, {
168
- id: pinned.id,
169
- name: 'before-auth',
170
- }),
171
- };
172
- };
173
-
174
- test('creates snapshots, pins them, and prunes only unpinned history', () => {
175
- const rootDir = makeRoot();
176
- const db = openWriteTrailsDb({ rootDir });
177
-
178
- try {
179
- const { disposable, pinned, pinnedSnapshot } = seedHistory(db);
180
-
181
- expect(pinnedSnapshot?.pinnedAs).toBe('before-auth');
182
- expect(
183
- listTopoSnapshots(db, { pinned: true }).map((snapshot) => snapshot.id)
184
- ).toEqual([pinned.id]);
185
- expect(listTopoSnapshots(db).map((snapshot) => snapshot.id)).toEqual([
186
- disposable.id,
187
- pinned.id,
188
- ]);
189
-
190
- expect(pruneUnpinnedSnapshots(db, { keep: 0 })).toBe(1);
191
- expect(listTopoSnapshots(db).map((snapshot) => snapshot.id)).toEqual([
192
- pinned.id,
193
- ]);
194
- } finally {
195
- db.close();
196
- }
197
- });
198
- });
@@ -1,82 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
-
3
- import {
4
- CancelledError,
5
- NetworkError,
6
- NotFoundError,
7
- ValidationError,
8
- errorCategories,
9
- exitCodeMap,
10
- jsonRpcCodeMap,
11
- statusCodeMap,
12
- } from '../errors.js';
13
- import {
14
- createTransportErrorMapper,
15
- mapTransportError,
16
- transportErrorMap,
17
- transportErrorRegistry,
18
- transportNames,
19
- } from '../transport-error-map.js';
20
-
21
- describe('transportErrorMap', () => {
22
- test('covers every error category for every transport', () => {
23
- for (const transport of transportNames) {
24
- const mappings = transportErrorMap[transport];
25
- for (const category of errorCategories) {
26
- expect(category in mappings).toBe(true);
27
- }
28
- }
29
- });
30
-
31
- test('reuses the existing public transport maps', () => {
32
- expect(transportErrorMap.cli).toBe(exitCodeMap);
33
- expect(transportErrorMap.http).toBe(statusCodeMap);
34
- expect(transportErrorMap.mcp).toBe(jsonRpcCodeMap);
35
- });
36
- });
37
-
38
- describe('transportErrorRegistry', () => {
39
- test('exposes a callable mapper for each transport', () => {
40
- const notFound = new NotFoundError('missing');
41
-
42
- expect(transportErrorRegistry.cli.map(notFound)).toBe(2);
43
- expect(transportErrorRegistry.http.map(notFound)).toBe(404);
44
- expect(transportErrorRegistry.mcp.map(notFound)).toBe(-32_601);
45
- });
46
- });
47
-
48
- describe('mapTransportError', () => {
49
- test('maps known error instances through each registered transport', () => {
50
- const validation = new ValidationError('bad input');
51
- const notFound = new NotFoundError('missing');
52
- const network = new NetworkError('offline');
53
- const cancelled = new CancelledError('cancelled');
54
-
55
- expect(mapTransportError('cli', validation)).toBe(1);
56
- expect(mapTransportError('http', notFound)).toBe(404);
57
- expect(mapTransportError('mcp', notFound)).toBe(-32_601);
58
- expect(mapTransportError('http', network)).toBe(502);
59
- expect(mapTransportError('cli', cancelled)).toBe(130);
60
- });
61
- });
62
-
63
- describe('createTransportErrorMapper', () => {
64
- test('uses the error category to project into transport-specific values', () => {
65
- const mapper = createTransportErrorMapper({
66
- auth: 'auth',
67
- cancelled: 'cancelled',
68
- conflict: 'conflict',
69
- internal: 'internal',
70
- network: 'network',
71
- not_found: 'not_found',
72
- permission: 'permission',
73
- rate_limit: 'rate_limit',
74
- timeout: 'timeout',
75
- validation: 'validation',
76
- });
77
-
78
- expect(mapper(new ValidationError('bad input'))).toBe('validation');
79
- expect(mapper(new NetworkError('offline'))).toBe('network');
80
- expect(mapper(new CancelledError('cancelled'))).toBe('cancelled');
81
- });
82
- });