@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,277 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
-
3
- import { z } from 'zod';
4
-
5
- import { ValidationError } from '../errors.js';
6
- import { provision } from '../provision.js';
7
- import { Result } from '../result.js';
8
- import { topo } from '../topo.js';
9
-
10
- // ---------------------------------------------------------------------------
11
- // Mock factories
12
- // ---------------------------------------------------------------------------
13
-
14
- // oxlint-disable-next-line require-await -- satisfies async interface without needing await
15
- const noop = async () => Result.ok();
16
-
17
- const mockTrail = (id: string, crosses?: readonly string[]) => ({
18
- blaze: noop,
19
- crosses: Object.freeze([...(crosses ?? [])]),
20
- id,
21
- input: z.object({ x: z.number() }),
22
- kind: 'trail' as const,
23
- output: z.object({ y: z.number() }),
24
- });
25
-
26
- const mockEvent = (id: string) => ({
27
- id,
28
- kind: 'signal' as const,
29
- payload: z.object({ payload: z.string() }),
30
- });
31
-
32
- const mockProvision = (id: string) =>
33
- provision(id, {
34
- create: () => Result.ok({ id }),
35
- description: `${id} provision`,
36
- });
37
-
38
- // ---------------------------------------------------------------------------
39
- // topo()
40
- // ---------------------------------------------------------------------------
41
-
42
- describe('topo', () => {
43
- describe('collection', () => {
44
- test('returns Topo with name', () => {
45
- const t = topo('my-app');
46
- expect(t.name).toBe('my-app');
47
- });
48
-
49
- test('collects trails from modules', () => {
50
- const mod = { myTrail: mockTrail('create-user') };
51
- const t = topo('app', mod);
52
-
53
- expect(t.trails.size).toBe(1);
54
- expect(t.trails.get('create-user')).toBe(mod.myTrail);
55
- });
56
-
57
- test('auto-scans exports by kind discriminant', () => {
58
- const mod = {
59
- event1: mockEvent('e1'),
60
- provision1: mockProvision('s1'),
61
- trail1: mockTrail('t1'),
62
- trail2: mockTrail('t2', ['t1']),
63
- };
64
- const t = topo('app', mod);
65
-
66
- expect(t.trails.size).toBe(2);
67
- expect(t.signals.size).toBe(1);
68
- expect(t.provisions.size).toBe(1);
69
- });
70
-
71
- test('collects from multiple modules', () => {
72
- const mod1 = { a: mockTrail('t1') };
73
- const mod2 = { b: mockTrail('t2'), c: mockTrail('t3', ['t1']) };
74
- const t = topo('app', mod1, mod2);
75
-
76
- expect(t.trails.size).toBe(3);
77
- });
78
-
79
- test('non-trail exports are silently ignored', () => {
80
- const mod = {
81
- config: { port: 3000 },
82
- helper: () => 'not a trail',
83
- name: 'some-string',
84
- nothing: null,
85
- num: 42,
86
- trail1: mockTrail('t1'),
87
- undef: undefined,
88
- };
89
- const t = topo('app', mod);
90
-
91
- expect(t.trails.size).toBe(1);
92
- expect(t.signals.size).toBe(0);
93
- expect(t.provisions.size).toBe(0);
94
- });
95
-
96
- test('trail with crossings registers correctly', () => {
97
- const mod = { t: mockTrail('trail-1', ['trail-2']) };
98
- const t = topo('app', mod);
99
-
100
- expect(t.trails.size).toBe(1);
101
- const registered = t.trails.get('trail-1');
102
- expect(registered?.crosses).toEqual(['trail-2']);
103
- });
104
-
105
- test('collects provisions from modules', () => {
106
- const mod = { db: mockProvision('db.main') };
107
- const t = topo('app', mod);
108
-
109
- expect(t.provisions.size).toBe(1);
110
- expect(t.provisions.get('db.main')).toBe(mod.db);
111
- });
112
- });
113
-
114
- describe('duplicate rejection', () => {
115
- test('rejects duplicate trail IDs', () => {
116
- const mod1 = { a: mockTrail('dup') };
117
- const mod2 = { b: mockTrail('dup') };
118
-
119
- expect(() => topo('app', mod1, mod2)).toThrow(ValidationError);
120
- expect(() => topo('app', mod1, mod2)).toThrow(
121
- 'Duplicate trail ID: "dup"'
122
- );
123
- });
124
-
125
- test('rejects duplicate event IDs', () => {
126
- const mod1 = { a: mockEvent('dup') };
127
- const mod2 = { b: mockEvent('dup') };
128
-
129
- expect(() => topo('app', mod1, mod2)).toThrow(ValidationError);
130
- expect(() => topo('app', mod1, mod2)).toThrow(
131
- 'Duplicate signal ID: "dup"'
132
- );
133
- });
134
-
135
- test('rejects duplicate provision IDs', () => {
136
- const mod1 = { a: mockProvision('dup') };
137
- const mod2 = { b: mockProvision('dup') };
138
-
139
- expect(() => topo('app', mod1, mod2)).toThrow(ValidationError);
140
- expect(() => topo('app', mod1, mod2)).toThrow(
141
- 'Duplicate provision ID: "dup"'
142
- );
143
- });
144
- });
145
- });
146
-
147
- // ---------------------------------------------------------------------------
148
- // topo accessors
149
- // ---------------------------------------------------------------------------
150
-
151
- describe('topo accessors', () => {
152
- test('ids() returns all trail IDs', () => {
153
- const a = mockTrail('alpha');
154
- const b = mockTrail('beta');
155
- const app = topo('test', { a, b });
156
- expect(app.ids().toSorted()).toEqual(['alpha', 'beta']);
157
- });
158
-
159
- test('count returns number of trails', () => {
160
- const a = mockTrail('alpha');
161
- const app = topo('test', { a });
162
- expect(app.count).toBe(1);
163
- });
164
-
165
- test('provisionCount returns number of provisions', () => {
166
- const db = mockProvision('db.main');
167
- const cache = mockProvision('cache.main');
168
- const app = topo('test', { cache, db });
169
- expect(app.provisionCount).toBe(2);
170
- });
171
-
172
- test('empty topo has zero count and empty ids', () => {
173
- const app = topo('empty');
174
- expect(app.count).toBe(0);
175
- expect(app.ids()).toEqual([]);
176
- expect(app.provisionCount).toBe(0);
177
- expect(app.provisionIds()).toEqual([]);
178
- });
179
-
180
- test('provisionIds() returns all provision IDs', () => {
181
- const db = mockProvision('db.main');
182
- const cache = mockProvision('cache.main');
183
- const app = topo('test', { cache, db });
184
- expect(app.provisionIds().toSorted()).toEqual(['cache.main', 'db.main']);
185
- });
186
- });
187
-
188
- // ---------------------------------------------------------------------------
189
- // Topo
190
- // ---------------------------------------------------------------------------
191
-
192
- describe('Topo', () => {
193
- const mod = {
194
- e1: mockEvent('event-1'),
195
- p1: mockProvision('provision-1'),
196
- t1: mockTrail('trail-1'),
197
- t2: mockTrail('trail-2'),
198
- t3: mockTrail('trail-3', ['trail-1']),
199
- };
200
-
201
- // Build once for the describe block
202
- const app = topo('app', mod);
203
-
204
- describe('get()', () => {
205
- test('retrieves trail by ID', () => {
206
- expect(app.get('trail-1')).toBe(mod.t1);
207
- });
208
-
209
- test('retrieves trail with crossings by ID', () => {
210
- expect(app.get('trail-3')).toBe(mod.t3);
211
- });
212
-
213
- test('returns undefined for unknown ID', () => {
214
- expect(app.get('nope')).toBeUndefined();
215
- });
216
- });
217
-
218
- describe('has()', () => {
219
- test('returns true for known trail', () => {
220
- expect(app.has('trail-1')).toBe(true);
221
- });
222
-
223
- test('returns true for trail with crossings', () => {
224
- expect(app.has('trail-3')).toBe(true);
225
- });
226
-
227
- test('returns false for unknown ID', () => {
228
- expect(app.has('nope')).toBe(false);
229
- });
230
-
231
- test('returns false for event ID (signals are not trails)', () => {
232
- expect(app.has('event-1')).toBe(false);
233
- });
234
- });
235
-
236
- describe('getProvision()', () => {
237
- test('retrieves provision by ID', () => {
238
- expect(app.getProvision('provision-1')).toBe(mod.p1);
239
- });
240
-
241
- test('returns undefined for unknown provision ID', () => {
242
- expect(app.getProvision('missing-provision')).toBeUndefined();
243
- });
244
- });
245
-
246
- describe('hasProvision()', () => {
247
- test('returns true for known provision', () => {
248
- expect(app.hasProvision('provision-1')).toBe(true);
249
- });
250
-
251
- test('returns false for unknown provision', () => {
252
- expect(app.hasProvision('missing-provision')).toBe(false);
253
- });
254
- });
255
-
256
- describe('listing', () => {
257
- test('list() returns all trails (with and without crossings)', () => {
258
- const items = app.list();
259
- expect(items).toHaveLength(3);
260
- expect(items).toContain(mod.t1);
261
- expect(items).toContain(mod.t2);
262
- expect(items).toContain(mod.t3);
263
- });
264
-
265
- test('listEvents() returns all signals', () => {
266
- const items = app.listSignals();
267
- expect(items).toHaveLength(1);
268
- expect(items).toContain(mod.e1);
269
- });
270
-
271
- test('listProvisions() returns all provisions', () => {
272
- const items = app.listProvisions();
273
- expect(items).toHaveLength(1);
274
- expect(items).toContain(mod.p1);
275
- });
276
- });
277
- });
@@ -1,60 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
-
3
- import { z } from 'zod';
4
-
5
- import { Result } from '../result';
6
- import { trail } from '../trail';
7
- import type { PermitRequirement } from '../types';
8
-
9
- describe('PermitRequirement type', () => {
10
- test('accepts a scopes object', () => {
11
- const req: PermitRequirement = { scopes: ['user:write', 'user:read'] };
12
- expect(req).toEqual({ scopes: ['user:write', 'user:read'] });
13
- });
14
-
15
- test('accepts public literal', () => {
16
- const req: PermitRequirement = 'public';
17
- expect(req).toBe('public');
18
- });
19
- });
20
-
21
- describe('trail() with permit field', () => {
22
- test('accepts permit with scopes', () => {
23
- const t = trail('user.delete', {
24
- blaze: (input) => Result.ok({ deleted: input.id }),
25
- input: z.object({ id: z.string() }),
26
- intent: 'destroy',
27
- permit: { scopes: ['user:write'] },
28
- });
29
- expect(t.permit).toEqual({ scopes: ['user:write'] });
30
- });
31
-
32
- test('accepts permit: public', () => {
33
- const t = trail('health.check', {
34
- blaze: () => Result.ok({ status: 'ok' }),
35
- input: z.object({}),
36
- intent: 'read',
37
- permit: 'public',
38
- });
39
- expect(t.permit).toBe('public');
40
- });
41
-
42
- test('permit is undefined when omitted (backward compatible)', () => {
43
- const t = trail('legacy.trail', {
44
- blaze: () => Result.ok(),
45
- input: z.object({}),
46
- });
47
- expect(t.permit).toBeUndefined();
48
- });
49
-
50
- test('preserves permit on the frozen Trail object', () => {
51
- const t = trail('user.list', {
52
- blaze: () => Result.ok([]),
53
- input: z.object({}),
54
- intent: 'read',
55
- permit: { scopes: ['user:read'] },
56
- });
57
- expect(Object.isFrozen(t)).toBe(true);
58
- expect(t.permit).toEqual({ scopes: ['user:read'] });
59
- });
60
- });
@@ -1,261 +0,0 @@
1
- import { describe, test, expect } from 'bun:test';
2
-
3
- import { z } from 'zod';
4
-
5
- import { createTrailContext } from '../context';
6
- import { Result } from '../result';
7
- import { provision } from '../provision';
8
- import { trail } from '../trail';
9
- import type { TrailContext } from '../types';
10
-
11
- const stubCtx: TrailContext = createTrailContext({
12
- abortSignal: AbortSignal.timeout(5000),
13
- requestId: 'test-123',
14
- });
15
-
16
- const dbProvision = provision('db.main', {
17
- create: () =>
18
- Result.ok({
19
- query(sql: string) {
20
- return sql.length;
21
- },
22
- }),
23
- description: 'Primary database provision',
24
- });
25
-
26
- describe('trail()', () => {
27
- const inputSchema = z.object({ name: z.string() });
28
- const outputSchema = z.object({ greeting: z.string() });
29
-
30
- const greet = trail('greet', {
31
- blaze: (input) => Result.ok({ greeting: `Hello, ${input.name}!` }),
32
- description: 'Greet someone',
33
- input: inputSchema,
34
- output: outputSchema,
35
- });
36
-
37
- describe('basics', () => {
38
- test('returns correct id', () => {
39
- expect(greet.id).toBe('greet');
40
- });
41
-
42
- test("returns kind 'trail'", () => {
43
- expect(greet.kind).toBe('trail');
44
- });
45
-
46
- test('preserves input schema', () => {
47
- const parsed = greet.input.safeParse({ name: 'World' });
48
- expect(parsed.success).toBe(true);
49
-
50
- const bad = greet.input.safeParse({ name: 42 });
51
- expect(bad.success).toBe(false);
52
- });
53
-
54
- test('output schema is optional', () => {
55
- const minimal = trail('noop', {
56
- blaze: () => Result.ok(),
57
- input: z.object({}),
58
- });
59
- expect(minimal.output).toBeUndefined();
60
- });
61
-
62
- test('implementation is callable', async () => {
63
- const result = await greet.blaze({ name: 'World' }, stubCtx);
64
- expect(result.isOk()).toBe(true);
65
- expect(result.unwrap()).toEqual({ greeting: 'Hello, World!' });
66
- });
67
- });
68
-
69
- describe('meta', () => {
70
- test('examples are stored', () => {
71
- const withExamples = trail('echo', {
72
- blaze: (input) => Result.ok({ text: input.text }),
73
- examples: [
74
- { error: 'ValidationError', input: { text: '' }, name: 'error-case' },
75
- { expected: { text: 'hi' }, input: { text: 'hi' }, name: 'basic' },
76
- ],
77
- input: z.object({ text: z.string() }),
78
- });
79
- expect(withExamples.examples).toHaveLength(2);
80
- const first = withExamples.examples?.[0];
81
- expect(first?.name).toBe('error-case');
82
- const second = withExamples.examples?.[1];
83
- expect(second?.name).toBe('basic');
84
- });
85
-
86
- test('meta is stored', () => {
87
- const withMeta = trail('tagged', {
88
- blaze: () => Result.ok(),
89
- input: z.object({}),
90
- meta: { domain: 'billing', tier: 1 },
91
- });
92
- expect(withMeta.meta).toEqual({ domain: 'billing', tier: 1 });
93
- });
94
-
95
- test('detours are stored', () => {
96
- const withDetours = trail('orchestrator', {
97
- blaze: () => Result.ok(),
98
- detours: {
99
- onFailure: ['alert'],
100
- onSuccess: ['notify', 'audit'],
101
- },
102
- input: z.object({}),
103
- });
104
- expect(withDetours.detours).toEqual({
105
- onFailure: ['alert'],
106
- onSuccess: ['notify', 'audit'],
107
- });
108
- });
109
- });
110
-
111
- describe('crosses', () => {
112
- test('defaults to empty frozen array when omitted', () => {
113
- const minimal = trail('bare', {
114
- blaze: () => Result.ok(),
115
- input: z.object({}),
116
- });
117
- expect(minimal.crosses).toEqual([]);
118
- expect(Object.isFrozen(minimal.crosses)).toBe(true);
119
- });
120
-
121
- test('preserves crosses array', () => {
122
- const withCrosses = trail('composed', {
123
- blaze: () => Result.ok(),
124
- crosses: ['authenticate', 'validate-session'],
125
- input: z.object({}),
126
- });
127
- expect(withCrosses.crosses).toEqual(['authenticate', 'validate-session']);
128
- });
129
-
130
- test('crosses array is frozen', () => {
131
- const withCrosses = trail('composed', {
132
- blaze: () => Result.ok(),
133
- crosses: ['authenticate'],
134
- input: z.object({}),
135
- });
136
- expect(Object.isFrozen(withCrosses.crosses)).toBe(true);
137
- });
138
- });
139
-
140
- describe('provisions', () => {
141
- test('defaults to empty frozen array when omitted', () => {
142
- const minimal = trail('bare', {
143
- blaze: () => Result.ok(),
144
- input: z.object({}),
145
- });
146
- expect(minimal.provisions).toEqual([]);
147
- expect(Object.isFrozen(minimal.provisions)).toBe(true);
148
- });
149
-
150
- test('preserves declared provision objects', () => {
151
- const withProvisions = trail('search', {
152
- blaze: () => Result.ok(),
153
- input: z.object({}),
154
- provisions: [dbProvision],
155
- });
156
- expect(withProvisions.provisions).toEqual([dbProvision]);
157
- expect(withProvisions.provisions[0]).toBe(dbProvision);
158
- });
159
-
160
- test('provisions array is frozen', () => {
161
- const withProvisions = trail('search', {
162
- blaze: () => Result.ok(),
163
- input: z.object({}),
164
- provisions: [dbProvision],
165
- });
166
- expect(Object.isFrozen(withProvisions.provisions)).toBe(true);
167
- });
168
- });
169
-
170
- describe('intent and idempotent', () => {
171
- test('intent defaults to write', () => {
172
- const minimal = trail('bare', {
173
- blaze: () => Result.ok(),
174
- input: z.object({}),
175
- });
176
- expect(minimal.intent).toBe('write');
177
- expect(minimal.idempotent).toBeUndefined();
178
- });
179
-
180
- test('intent is preserved when set', () => {
181
- const readTrail = trail('reader', {
182
- blaze: () => Result.ok(),
183
- input: z.object({}),
184
- intent: 'read',
185
- });
186
- expect(readTrail.intent).toBe('read');
187
-
188
- const destroyTrail = trail('destroyer', {
189
- blaze: () => Result.ok(),
190
- input: z.object({}),
191
- intent: 'destroy',
192
- });
193
- expect(destroyTrail.intent).toBe('destroy');
194
- });
195
-
196
- test('idempotent is preserved when set', () => {
197
- const t = trail('idempotent', {
198
- blaze: () => Result.ok(),
199
- idempotent: true,
200
- input: z.object({}),
201
- });
202
- expect(t.idempotent).toBe(true);
203
- });
204
- });
205
-
206
- describe('single-object overload', () => {
207
- test('accepts spec with id property', () => {
208
- const t = trail({
209
- blaze: (input: { name: string }, _ctx: TrailContext) =>
210
- Result.ok({ greeting: `Hi, ${input.name}` }),
211
- id: 'entity.show',
212
- input: inputSchema,
213
- });
214
- expect(t.id).toBe('entity.show');
215
- expect(t.kind).toBe('trail');
216
- });
217
-
218
- test('preserves all spec fields', () => {
219
- const t = trail({
220
- blaze: (input: { name: string }, _ctx: TrailContext) =>
221
- Result.ok({ greeting: `Hi, ${input.name}` }),
222
- description: 'A full trail',
223
- examples: [{ input: { name: 'World' }, name: 'test' }],
224
- id: 'full',
225
- input: inputSchema,
226
- intent: 'read',
227
- output: outputSchema,
228
- provisions: [dbProvision],
229
- });
230
- expect(t.description).toBe('A full trail');
231
- expect(t.intent).toBe('read');
232
- expect(t.examples).toHaveLength(1);
233
- expect(t.provisions).toEqual([dbProvision]);
234
- });
235
-
236
- test('implementation is callable', async () => {
237
- const t = trail({
238
- blaze: (input: { x: number }) => Result.ok(input.x * 2),
239
- id: 'callable',
240
- input: z.object({ x: z.number() }),
241
- });
242
- const result = await t.blaze({ x: 5 }, stubCtx);
243
- expect(result.isOk()).toBe(true);
244
- expect(result.unwrap()).toBe(10);
245
- });
246
-
247
- test('sync implementations are normalized to an awaitable runtime function', async () => {
248
- const t = trail('normalized', {
249
- blaze: (input: { value: number }) => Result.ok(input.value + 1),
250
- input: z.object({ value: z.number() }),
251
- });
252
-
253
- const promise = t.blaze({ value: 2 }, stubCtx);
254
- expect(promise).toBeInstanceOf(Promise);
255
-
256
- const result = await promise;
257
- expect(result.isOk()).toBe(true);
258
- expect(result.unwrap()).toBe(3);
259
- });
260
- });
261
- });