@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,197 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { z } from 'zod';
3
-
4
- import {
5
- Result,
6
- createTrailContext,
7
- findDuplicateProvisionId,
8
- isProvision,
9
- provision as defineProvision,
10
- } from '../index.js';
11
- import type {
12
- Provision,
13
- ProvisionContext,
14
- ProvisionSpec,
15
- TrailContext,
16
- } from '../index.js';
17
-
18
- const provisionCtx: ProvisionContext = {
19
- cwd: '/tmp/trails',
20
- env: { DATABASE_URL: 'file::memory:' },
21
- workspaceRoot: '/tmp',
22
- };
23
-
24
- let disposedValue: number | undefined;
25
-
26
- const counterProvisionSpec: ProvisionSpec<number> = {
27
- create: () => Result.ok(3),
28
- description: 'Counter provision',
29
- dispose: (provision) => {
30
- disposedValue = provision;
31
- },
32
- health: (provision) => Result.ok({ healthy: provision > 0 }),
33
- meta: { domain: 'data' },
34
- mock: () => 1,
35
- };
36
-
37
- const resolvedServiceCtx = (id: string, instance: unknown): TrailContext =>
38
- createTrailContext({
39
- abortSignal: new AbortController().signal,
40
- extensions: { [id]: instance },
41
- requestId: `${id}-request`,
42
- });
43
-
44
- describe('provision types', () => {
45
- test('ProvisionContext exposes the stable process-scoped fields', () => {
46
- expect(provisionCtx.cwd).toBe('/tmp/trails');
47
- expect(provisionCtx.env?.DATABASE_URL).toBe('file::memory:');
48
- expect(provisionCtx.workspaceRoot).toBe('/tmp');
49
- });
50
-
51
- test('ProvisionSpec stores description and meta', () => {
52
- expect(counterProvisionSpec.description).toBe('Counter provision');
53
- expect(counterProvisionSpec.meta).toEqual({ domain: 'data' });
54
- });
55
-
56
- test('ProvisionSpec can reserve a config schema for future composition', () => {
57
- const config = z.object({ url: z.string().url() });
58
- const spec: ProvisionSpec<number> = {
59
- config,
60
- create: () => Result.ok(1),
61
- };
62
-
63
- expect(spec.config).toBe(config);
64
- });
65
-
66
- test('ProvisionSpec create and health callbacks are callable', async () => {
67
- const result = await counterProvisionSpec.create(provisionCtx);
68
- expect(result.isOk()).toBe(true);
69
- expect(result.unwrap()).toBe(3);
70
-
71
- const health = await counterProvisionSpec.health?.(result.unwrap());
72
- expect(health?.isOk()).toBe(true);
73
- expect(health?.unwrap()).toEqual({ healthy: true });
74
- });
75
-
76
- test('ProvisionSpec mock and dispose callbacks are callable', async () => {
77
- disposedValue = undefined;
78
- const result = await counterProvisionSpec.create(provisionCtx);
79
- expect(result.isOk()).toBe(true);
80
-
81
- const provision = result.unwrap();
82
- expect(await counterProvisionSpec.mock?.()).toBe(1);
83
-
84
- await counterProvisionSpec.dispose?.(provision);
85
- expect(disposedValue).toBe(3);
86
- });
87
-
88
- test('ProvisionSpec create can be async', async () => {
89
- const spec: ProvisionSpec<number> = {
90
- create: async () => {
91
- await Bun.sleep(0);
92
- return Result.ok(7);
93
- },
94
- };
95
-
96
- const result = await spec.create(provisionCtx);
97
- expect(result.isOk()).toBe(true);
98
- expect(result.unwrap()).toBe(7);
99
- });
100
-
101
- test('Provision carries identity alongside the shared spec fields', async () => {
102
- const counterProvision: Provision<number> = {
103
- from(ctx) {
104
- return ctx.provision(this);
105
- },
106
- id: 'counter.main',
107
- kind: 'provision',
108
- ...counterProvisionSpec,
109
- };
110
-
111
- expect(counterProvision.kind).toBe('provision');
112
- expect(counterProvision.id).toBe('counter.main');
113
-
114
- const created = await counterProvision.create(provisionCtx);
115
- expect(created.isOk()).toBe(true);
116
- expect(created.unwrap()).toBe(3);
117
- expect(await counterProvision.mock?.()).toBe(1);
118
- });
119
- });
120
-
121
- describe('provision()', () => {
122
- test('returns a frozen provision object with kind and id', () => {
123
- const counter = defineProvision('counter.main', counterProvisionSpec);
124
-
125
- expect(counter.kind).toBe('provision');
126
- expect(counter.id).toBe('counter.main');
127
- expect(Object.isFrozen(counter)).toBe(true);
128
- });
129
-
130
- test('infers the provision type through from(ctx)', () => {
131
- const db = defineProvision('db.main', {
132
- create: () =>
133
- Result.ok({
134
- query(sql: string) {
135
- return sql.length;
136
- },
137
- }),
138
- description: 'Typed database service',
139
- });
140
-
141
- const store = {
142
- query(sql: string) {
143
- return sql.length;
144
- },
145
- };
146
- const ctx = resolvedServiceCtx('db.main', store);
147
-
148
- const resolved = db.from(ctx);
149
- expect(resolved.query('select 1')).toBe(8);
150
- });
151
-
152
- test('from(ctx) throws when the provision is missing', () => {
153
- const counter = defineProvision('counter.main', counterProvisionSpec);
154
- const ctx = createTrailContext({
155
- abortSignal: new AbortController().signal,
156
- requestId: 'missing-provision',
157
- });
158
-
159
- expect(() => counter.from(ctx)).toThrow(
160
- 'Provision "counter.main" not found in trail context'
161
- );
162
- });
163
-
164
- test('from(ctx) returns undefined when the provision key exists with an undefined value', () => {
165
- const optional = defineProvision<undefined>('optional.main', {
166
- create: () => Result.ok<undefined>(),
167
- });
168
- const ctx = createTrailContext({
169
- abortSignal: new AbortController().signal,
170
- extensions: { [optional.id]: undefined },
171
- requestId: 'undefined-provision',
172
- });
173
-
174
- expect(optional.from(ctx)).toBeUndefined();
175
- });
176
- });
177
-
178
- describe('provision helpers', () => {
179
- test('isProvision identifies provision definitions', () => {
180
- const counter = defineProvision('counter.main', counterProvisionSpec);
181
-
182
- expect(isProvision(counter)).toBe(true);
183
- expect(isProvision({ id: 'counter.main', kind: 'trail' })).toBe(false);
184
- expect(isProvision(null)).toBe(false);
185
- });
186
-
187
- test('findDuplicateProvisionId returns the first repeated ID', () => {
188
- const first = defineProvision('counter.main', counterProvisionSpec);
189
- const duplicate = defineProvision('counter.main', counterProvisionSpec);
190
- const other = defineProvision('counter.secondary', counterProvisionSpec);
191
-
192
- expect(findDuplicateProvisionId([first, other])).toBeUndefined();
193
- expect(findDuplicateProvisionId([first, other, duplicate])).toBe(
194
- 'counter.main'
195
- );
196
- });
197
- });
@@ -1,82 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
-
3
- import { z } from 'zod';
4
-
5
- import { signal } from '../signal.js';
6
-
7
- const payloadSchema = z.object({
8
- action: z.string(),
9
- userId: z.string(),
10
- });
11
-
12
- const userAction = signal('user.action', {
13
- description: 'A user performed an action',
14
- from: ['auth.login', 'auth.signup'],
15
- meta: { domain: 'auth', priority: 1 },
16
- payload: payloadSchema,
17
- });
18
-
19
- describe('signal() basics', () => {
20
- test("returns kind 'signal'", () => {
21
- expect(userAction.kind).toBe('signal');
22
- });
23
-
24
- test('returns correct id', () => {
25
- expect(userAction.id).toBe('user.action');
26
- });
27
-
28
- test('preserves payload schema', () => {
29
- const parsed = userAction.payload.safeParse({
30
- action: 'click',
31
- userId: 'u-1',
32
- });
33
- expect(parsed.success).toBe(true);
34
-
35
- const bad = userAction.payload.safeParse({ userId: 42 });
36
- expect(bad.success).toBe(false);
37
- });
38
-
39
- test('preserves description', () => {
40
- expect(userAction.description).toBe('A user performed an action');
41
- });
42
-
43
- test('result object is frozen', () => {
44
- expect(Object.isFrozen(userAction)).toBe(true);
45
- });
46
- });
47
-
48
- describe('signal() from and meta', () => {
49
- test('preserves from', () => {
50
- expect(userAction.from).toEqual(['auth.login', 'auth.signup']);
51
- });
52
-
53
- test('from array is frozen', () => {
54
- expect(Object.isFrozen(userAction.from)).toBe(true);
55
- });
56
-
57
- test('preserves meta', () => {
58
- expect(userAction.meta).toEqual({ domain: 'auth', priority: 1 });
59
- });
60
-
61
- test('optional fields default to undefined', () => {
62
- const minimal = signal('minimal', {
63
- payload: z.string(),
64
- });
65
- expect(minimal.description).toBeUndefined();
66
- expect(minimal.from).toBeUndefined();
67
- expect(minimal.meta).toBeUndefined();
68
- });
69
- });
70
-
71
- describe('signal() single-object overload', () => {
72
- test('accepts spec with id property', () => {
73
- const e = signal({
74
- from: ['entity.add', 'entity.delete'],
75
- id: 'entity.updated',
76
- payload: z.object({ entityId: z.string() }),
77
- });
78
- expect(e.id).toBe('entity.updated');
79
- expect(e.kind).toBe('signal');
80
- expect(e.from).toEqual(['entity.add', 'entity.delete']);
81
- });
82
- });
@@ -1,251 +0,0 @@
1
- import { afterEach, describe, expect, test } from 'bun:test';
2
- import { mkdtempSync, rmSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
-
6
- import { z } from 'zod';
7
-
8
- import {
9
- createMockTopoStore,
10
- createTopoStore,
11
- provision,
12
- Result,
13
- signal,
14
- topo,
15
- topoStore,
16
- trail,
17
- } from '../index.js';
18
- import { pinTopoSave } from '../internal/topo-saves.js';
19
- import type { TopoSaveRecord } from '../internal/topo-saves.js';
20
- import { persistEstablishedTopoSave } from '../internal/topo-store.js';
21
- import { openWriteTrailsDb } from '../internal/trails-db.js';
22
-
23
- const noop = () => Result.ok({ ok: true });
24
-
25
- const expectOk = async <T>(
26
- result: Promise<Result<T, Error>> | Result<T, Error>
27
- ): Promise<T> => {
28
- const resolved = await result;
29
- if (resolved.isErr()) {
30
- throw resolved.error;
31
- }
32
- return resolved.value;
33
- };
34
-
35
- const requireValue = <T>(value: T | undefined, message: string): T => {
36
- if (value === undefined) {
37
- throw new Error(message);
38
- }
39
- return value;
40
- };
41
-
42
- const exampleApp = () => {
43
- const dbMain = provision('db.main', {
44
- create: () => Result.ok({ source: 'factory' }),
45
- description: 'Primary database',
46
- health: () => Result.ok({ ok: true }),
47
- mock: () => ({ source: 'mock' }),
48
- });
49
-
50
- const entityAdded = signal('entity.added', {
51
- description: 'An entity was added',
52
- from: ['entity.add'],
53
- payload: z.object({ id: z.string() }),
54
- });
55
-
56
- const entityAdd = trail('entity.add', {
57
- blaze: (input: { readonly name: string }) =>
58
- Result.ok({ id: input.name.toLowerCase(), ok: true }),
59
- description: 'Add a new entity',
60
- examples: [
61
- {
62
- expected: { id: 'ada', ok: true },
63
- input: { name: 'Ada' },
64
- name: 'Add Ada',
65
- },
66
- ],
67
- input: z.object({ name: z.string() }),
68
- output: z.object({ id: z.string(), ok: z.boolean() }),
69
- provisions: [dbMain],
70
- });
71
-
72
- const entityList = trail('entity.list', {
73
- blaze: noop,
74
- crosses: ['entity.add'],
75
- description: 'List entities',
76
- detours: {
77
- ConflictError: ['entity.add'],
78
- },
79
- idempotent: true,
80
- input: z.object({}),
81
- intent: 'read',
82
- output: z.object({ ok: z.boolean() }),
83
- provisions: [dbMain],
84
- });
85
-
86
- return topo('projection-app', {
87
- dbMain,
88
- entityAdd,
89
- entityAdded,
90
- entityList,
91
- });
92
- };
93
-
94
- describe('read-only topo store', () => {
95
- let tmpRoot: string | undefined;
96
-
97
- afterEach(() => {
98
- if (tmpRoot) {
99
- rmSync(tmpRoot, { force: true, recursive: true });
100
- tmpRoot = undefined;
101
- }
102
- });
103
-
104
- const makeRoot = (): string => {
105
- tmpRoot = mkdtempSync(join(tmpdir(), 'topo-store-read-'));
106
- return tmpRoot;
107
- };
108
-
109
- const seedStore = (): {
110
- readonly rootDir: string;
111
- readonly save: TopoSaveRecord;
112
- } => {
113
- const rootDir = makeRoot();
114
- const db = openWriteTrailsDb({ rootDir });
115
-
116
- try {
117
- const result = persistEstablishedTopoSave(db, exampleApp(), {
118
- createdAt: '2026-04-03T14:00:00.000Z',
119
- gitSha: 'abc123',
120
- });
121
- if (result.isErr()) {
122
- throw result.error;
123
- }
124
- const save = result.value;
125
- pinTopoSave(db, { name: 'baseline', saveId: save.id });
126
- return { rootDir, save };
127
- } finally {
128
- db.close();
129
- }
130
- };
131
-
132
- test('lists save-scoped trails and provisions through typed accessors', () => {
133
- const { rootDir, save } = seedStore();
134
- const store = createTopoStore({ rootDir });
135
-
136
- expect(store.saves.latest()?.id).toBe(save.id);
137
- expect(store.pins.get('baseline')?.saveId).toBe(save.id);
138
-
139
- expect(store.trails.list({ save: { saveId: save.id } })).toEqual([
140
- expect.objectContaining({
141
- description: 'Add a new entity',
142
- exampleCount: 1,
143
- hasOutput: true,
144
- id: 'entity.add',
145
- intent: 'write',
146
- }),
147
- expect.objectContaining({
148
- description: 'List entities',
149
- exampleCount: 0,
150
- id: 'entity.list',
151
- intent: 'read',
152
- safety: 'read',
153
- }),
154
- ]);
155
-
156
- expect(store.provisions.list({ save: { saveId: save.id } })).toEqual([
157
- expect.objectContaining({
158
- description: 'Primary database',
159
- health: 'available',
160
- id: 'db.main',
161
- usedBy: ['entity.add', 'entity.list'],
162
- }),
163
- ]);
164
- });
165
-
166
- test('filters trails by intent', () => {
167
- const { rootDir, save } = seedStore();
168
- const store = createTopoStore({ rootDir });
169
-
170
- const writeTrails = store.trails.list({
171
- intent: 'write',
172
- save: { saveId: save.id },
173
- });
174
- expect(writeTrails).toHaveLength(1);
175
- expect(writeTrails[0]?.id).toBe('entity.add');
176
-
177
- const readTrails = store.trails.list({
178
- intent: 'read',
179
- save: { saveId: save.id },
180
- });
181
- expect(readTrails).toHaveLength(1);
182
- expect(readTrails[0]?.id).toBe('entity.list');
183
- });
184
-
185
- test('returns detailed trail and export views, plus a query escape hatch', () => {
186
- const { rootDir, save } = seedStore();
187
- const store = createTopoStore({ rootDir });
188
-
189
- const detail = store.trails.get('entity.list', {
190
- save: { saveId: save.id },
191
- });
192
- expect(detail).toEqual(
193
- expect.objectContaining({
194
- crosses: ['entity.add'],
195
- detours: { ConflictError: ['entity.add'] },
196
- id: 'entity.list',
197
- provisions: ['db.main'],
198
- })
199
- );
200
- expect(detail?.examples).toEqual([]);
201
-
202
- const exported = store.exports.get({ pin: 'baseline' });
203
- expect(exported?.save.id).toBe(save.id);
204
- expect(exported?.trailheadHash).toHaveLength(64);
205
-
206
- const rows = store.query<{ id: string }>(
207
- 'SELECT id FROM topo_trails WHERE save_id = ? ORDER BY id ASC',
208
- [save.id]
209
- );
210
- expect(rows).toEqual([{ id: 'entity.add' }, { id: 'entity.list' }]);
211
- });
212
-
213
- test('fails loudly when no saved topo state exists', () => {
214
- const rootDir = makeRoot();
215
- const store = createTopoStore({ rootDir });
216
-
217
- expect(() => store.saves.latest()).toThrow('No saved topo state found');
218
- expect(() => store.trails.list()).toThrow('No saved topo state found');
219
- expect(() => store.exports.get()).toThrow('No saved topo state found');
220
- });
221
-
222
- test('exposes a provision factory and mock for topoStore', async () => {
223
- const { rootDir } = seedStore();
224
-
225
- const created = await expectOk(
226
- topoStore.create({
227
- config: undefined,
228
- cwd: rootDir,
229
- env: {},
230
- workspaceRoot: rootDir,
231
- })
232
- );
233
-
234
- expect(created.saves.latest()).toBeDefined();
235
- expect(
236
- created.query<{ count: number }>(
237
- 'SELECT COUNT(*) as count FROM topo_saves'
238
- )[0]?.count
239
- ).toBe(1);
240
-
241
- expect(topoStore.mock).toBeDefined();
242
- const mock = await requireValue(
243
- topoStore.mock,
244
- 'Expected topoStore to define a mock factory'
245
- )();
246
- expect(mock).toBeDefined();
247
- expect(() =>
248
- createMockTopoStore().query('SELECT id FROM topo_trails')
249
- ).toThrow('Mock topoStore.query() is unsupported');
250
- });
251
- });