@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,469 +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 { Result, provision, signal, topo, trail } from '../index.js';
9
- import {
10
- ensureTopoHistorySchema,
11
- pinTopoSave,
12
- pruneUnpinnedTopoSaves,
13
- } from '../internal/topo-saves.js';
14
- import {
15
- getStoredTopoExport,
16
- persistEstablishedTopoSave,
17
- } from '../internal/topo-store.js';
18
- import { openWriteTrailsDb } from '../internal/trails-db.js';
19
-
20
- const noop = () => Result.ok({ ok: true });
21
-
22
- /** Unwrap a Result in tests, throwing on Err. */
23
- const unwrap = <T>(result: Result<T, Error>): T => {
24
- if (result.isErr()) {
25
- throw result.error;
26
- }
27
- return result.value;
28
- };
29
-
30
- const countRows = (
31
- db: ReturnType<typeof openWriteTrailsDb>,
32
- tableName: string,
33
- saveId?: string
34
- ): number => {
35
- const row =
36
- saveId === undefined
37
- ? db
38
- .query<{ count: number }, []>(
39
- `SELECT COUNT(*) as count FROM ${tableName}`
40
- )
41
- .get()
42
- : db
43
- .query<{ count: number }, [string]>(
44
- `SELECT COUNT(*) as count FROM ${tableName} WHERE save_id = ?`
45
- )
46
- .get(saveId);
47
- return row?.count ?? 0;
48
- };
49
-
50
- const tableExists = (
51
- db: ReturnType<typeof openWriteTrailsDb>,
52
- tableName: string
53
- ): boolean => {
54
- const row = db
55
- .query<{ name: string }, [string]>(
56
- "SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?"
57
- )
58
- .get(tableName);
59
- return row?.name === tableName;
60
- };
61
-
62
- const exampleApp = () => {
63
- const dbMain = provision('db.main', {
64
- create: () => Result.ok({ source: 'factory' }),
65
- description: 'Primary database',
66
- health: () => Result.ok({ ok: true }),
67
- mock: () => ({ source: 'mock' }),
68
- });
69
-
70
- const searchIndex = provision('search.index', {
71
- create: () => Result.ok({ source: 'factory' }),
72
- mock: () => ({ source: 'mock' }),
73
- });
74
-
75
- const entityAdded = signal('entity.added', {
76
- description: 'An entity was added',
77
- from: ['entity.add'],
78
- payload: z.object({ id: z.string() }),
79
- });
80
-
81
- const entityAdd = trail('entity.add', {
82
- blaze: (input: { readonly name: string }) =>
83
- Result.ok({ id: input.name.toLowerCase(), ok: true }),
84
- description: 'Add a new entity',
85
- examples: [
86
- {
87
- expected: { id: 'ada', ok: true },
88
- input: { name: 'Ada' },
89
- name: 'Add Ada',
90
- },
91
- {
92
- error: 'ConflictError',
93
- input: { name: 'Existing' },
94
- name: 'Conflict on duplicate',
95
- },
96
- ],
97
- input: z.object({ name: z.string() }),
98
- meta: { owner: 'core', tags: ['write', 'entity'] },
99
- output: z.object({ id: z.string(), ok: z.boolean() }),
100
- provisions: [dbMain, searchIndex],
101
- });
102
-
103
- const entityList = trail('entity.list', {
104
- blaze: () => Result.ok({ items: ['ada'] }),
105
- crosses: ['entity.add'],
106
- description: 'List entities',
107
- idempotent: true,
108
- input: z.object({}),
109
- intent: 'read',
110
- output: z.object({ items: z.array(z.string()) }),
111
- provisions: [dbMain],
112
- });
113
-
114
- return topo('projection-app', {
115
- dbMain,
116
- entityAdd,
117
- entityAdded,
118
- entityList,
119
- searchIndex,
120
- });
121
- };
122
-
123
- const readTrailRows = (
124
- db: ReturnType<typeof openWriteTrailsDb>,
125
- saveId: string
126
- ) =>
127
- db
128
- .query<
129
- {
130
- description: string | null;
131
- example_count: number;
132
- has_output: number;
133
- id: string;
134
- idempotent: number;
135
- intent: string;
136
- meta: string | null;
137
- },
138
- [string]
139
- >(
140
- `SELECT id, intent, idempotent, has_output, example_count, description, meta
141
- FROM topo_trails
142
- WHERE save_id = ?
143
- ORDER BY id ASC`
144
- )
145
- .all(saveId);
146
-
147
- const readTrailSignalRows = (
148
- db: ReturnType<typeof openWriteTrailsDb>,
149
- saveId: string
150
- ) =>
151
- db
152
- .query<{ signal_id: string; trail_id: string }, [string]>(
153
- `SELECT trail_id, signal_id
154
- FROM topo_trail_signals
155
- WHERE save_id = ?`
156
- )
157
- .all(saveId);
158
-
159
- const readTrailheadRows = (
160
- db: ReturnType<typeof openWriteTrailsDb>,
161
- saveId: string
162
- ) =>
163
- db
164
- .query<{ derived_name: string; trail_id: string }, [string]>(
165
- `SELECT trail_id, derived_name
166
- FROM topo_trailheads
167
- WHERE save_id = ?
168
- ORDER BY trail_id ASC`
169
- )
170
- .all(saveId);
171
-
172
- const readExampleRows = (
173
- db: ReturnType<typeof openWriteTrailsDb>,
174
- saveId: string
175
- ) =>
176
- db
177
- .query<
178
- {
179
- error: string | null;
180
- expected: string | null;
181
- input: string;
182
- name: string;
183
- ordinal: number;
184
- },
185
- [string]
186
- >(
187
- `SELECT ordinal, name, input, expected, error
188
- FROM topo_examples
189
- WHERE save_id = ?
190
- ORDER BY ordinal ASC`
191
- )
192
- .all(saveId);
193
-
194
- const readProjectedTrailIds = (
195
- db: ReturnType<typeof openWriteTrailsDb>,
196
- saveId: string
197
- ) =>
198
- db
199
- .query<{ id: string }, [string]>(
200
- 'SELECT id FROM topo_trails WHERE save_id = ? ORDER BY id ASC'
201
- )
202
- .all(saveId)
203
- .map((row) => row.id);
204
-
205
- const requireStoredExport = (
206
- db: ReturnType<typeof openWriteTrailsDb>,
207
- saveId: string
208
- ) => {
209
- const stored = getStoredTopoExport(db, saveId);
210
- if (stored === undefined) {
211
- throw new Error(`Expected stored topo export for save "${saveId}"`);
212
- }
213
- return stored;
214
- };
215
-
216
- const expectProjectionCounts = (
217
- db: ReturnType<typeof openWriteTrailsDb>,
218
- saveId: string
219
- ): void => {
220
- expect(countRows(db, 'topo_trails', saveId)).toBe(2);
221
- expect(countRows(db, 'topo_crossings', saveId)).toBe(1);
222
- expect(countRows(db, 'topo_trail_provisions', saveId)).toBe(3);
223
- expect(countRows(db, 'topo_provisions', saveId)).toBe(2);
224
- expect(countRows(db, 'topo_signals', saveId)).toBe(1);
225
- expect(countRows(db, 'topo_trail_signals', saveId)).toBe(1);
226
- expect(countRows(db, 'topo_trailheads', saveId)).toBe(2);
227
- expect(countRows(db, 'topo_examples', saveId)).toBe(2);
228
- expect(countRows(db, 'topo_schemas', saveId)).toBe(5);
229
- expect(countRows(db, 'topo_exports')).toBeGreaterThanOrEqual(1);
230
- };
231
-
232
- const expectProjectedFixtureRows = (
233
- db: ReturnType<typeof openWriteTrailsDb>,
234
- saveId: string
235
- ): void => {
236
- expect(readTrailRows(db, saveId)).toEqual([
237
- {
238
- description: 'Add a new entity',
239
- example_count: 2,
240
- has_output: 1,
241
- id: 'entity.add',
242
- idempotent: 0,
243
- intent: 'write',
244
- meta: JSON.stringify({
245
- owner: 'core',
246
- tags: ['write', 'entity'],
247
- }),
248
- },
249
- {
250
- description: 'List entities',
251
- example_count: 0,
252
- has_output: 1,
253
- id: 'entity.list',
254
- idempotent: 1,
255
- intent: 'read',
256
- meta: null,
257
- },
258
- ]);
259
- expect(readTrailSignalRows(db, saveId)).toEqual([
260
- { signal_id: 'entity.added', trail_id: 'entity.add' },
261
- ]);
262
- expect(readTrailheadRows(db, saveId)).toEqual([
263
- { derived_name: 'entity add', trail_id: 'entity.add' },
264
- { derived_name: 'entity list', trail_id: 'entity.list' },
265
- ]);
266
- expect(readExampleRows(db, saveId)).toEqual([
267
- {
268
- error: null,
269
- expected: JSON.stringify({ id: 'ada', ok: true }),
270
- input: JSON.stringify({ name: 'Ada' }),
271
- name: 'Add Ada',
272
- ordinal: 0,
273
- },
274
- {
275
- error: 'ConflictError',
276
- expected: null,
277
- input: JSON.stringify({ name: 'Existing' }),
278
- name: 'Conflict on duplicate',
279
- ordinal: 1,
280
- },
281
- ]);
282
- };
283
-
284
- const simpleProjectionApp = (withList: boolean) =>
285
- topo('projection-app', {
286
- entityAdd: trail('entity.add', {
287
- blaze: noop,
288
- input: z.object({}),
289
- output: z.object({ ok: z.boolean() }),
290
- }),
291
- ...(withList
292
- ? {
293
- entityList: trail('entity.list', {
294
- blaze: () => Result.ok({ items: ['one'] }),
295
- input: z.object({}),
296
- intent: 'read',
297
- output: z.object({ items: z.array(z.string()) }),
298
- }),
299
- }
300
- : {}),
301
- });
302
-
303
- const seedHistoryOnlyTopoSchema = (
304
- db: ReturnType<typeof openWriteTrailsDb>
305
- ): void => {
306
- db.run(
307
- `INSERT INTO meta_schema_versions (subsystem, version, updated_at)
308
- VALUES ('topo', 1, ?)`,
309
- ['2026-04-03T11:00:00.000Z']
310
- );
311
- db.run(`CREATE TABLE IF NOT EXISTS topo_saves (
312
- id TEXT PRIMARY KEY,
313
- git_sha TEXT,
314
- git_dirty INTEGER NOT NULL DEFAULT 0,
315
- trail_count INTEGER NOT NULL DEFAULT 0,
316
- signal_count INTEGER NOT NULL DEFAULT 0,
317
- provision_count INTEGER NOT NULL DEFAULT 0,
318
- created_at TEXT NOT NULL
319
- )`);
320
- db.run(`CREATE TABLE IF NOT EXISTS topo_pins (
321
- name TEXT PRIMARY KEY,
322
- save_id TEXT NOT NULL UNIQUE,
323
- created_at TEXT NOT NULL
324
- )`);
325
- db.run(
326
- `INSERT INTO topo_saves (
327
- id, git_sha, git_dirty, trail_count, signal_count, provision_count, created_at
328
- ) VALUES (?, ?, ?, ?, ?, ?, ?)`,
329
- ['seed-save', 'seed123', 0, 1, 0, 0, '2026-04-03T11:00:00.000Z']
330
- );
331
- db.run('INSERT INTO topo_pins (name, save_id, created_at) VALUES (?, ?, ?)', [
332
- 'seed-pin',
333
- 'seed-save',
334
- '2026-04-03T11:01:00.000Z',
335
- ]);
336
- };
337
-
338
- describe('topo store projection', () => {
339
- let tmpRoot: string | undefined;
340
-
341
- afterEach(() => {
342
- if (tmpRoot) {
343
- rmSync(tmpRoot, { force: true, recursive: true });
344
- tmpRoot = undefined;
345
- }
346
- });
347
-
348
- const makeRoot = (): string => {
349
- tmpRoot = mkdtempSync(join(tmpdir(), 'topo-store-'));
350
- return tmpRoot;
351
- };
352
-
353
- const withProjectionDb = (
354
- run: (db: ReturnType<typeof openWriteTrailsDb>) => void
355
- ): void => {
356
- const db = openWriteTrailsDb({ rootDir: makeRoot() });
357
- try {
358
- run(db);
359
- } finally {
360
- db.close();
361
- }
362
- };
363
-
364
- test('projects a save-scoped relational topo from the established app graph', () => {
365
- withProjectionDb((db) => {
366
- const save = unwrap(
367
- persistEstablishedTopoSave(db, exampleApp(), {
368
- createdAt: '2026-04-03T12:00:00.000Z',
369
- gitDirty: false,
370
- gitSha: 'abc123',
371
- })
372
- );
373
- expectProjectionCounts(db, save.id);
374
- expectProjectedFixtureRows(db, save.id);
375
-
376
- const stored = requireStoredExport(db, save.id);
377
- expect(JSON.parse(stored.trailheadMapJson)).toMatchObject({
378
- entries: expect.any(Array),
379
- generatedAt: '2026-04-03T12:00:00.000Z',
380
- version: '1.0',
381
- });
382
- expect(JSON.parse(stored.lockContent)).toMatchObject({
383
- apps: {
384
- 'projection-app': {
385
- provisions: expect.any(Object),
386
- signals: expect.any(Object),
387
- trails: expect.any(Object),
388
- },
389
- },
390
- generatedAt: '2026-04-03T12:00:00.000Z',
391
- hash: stored.trailheadHash,
392
- version: 1,
393
- });
394
- });
395
- });
396
-
397
- test('keeps projected rows isolated across successive saves', () => {
398
- withProjectionDb((db) => {
399
- const firstSave = unwrap(
400
- persistEstablishedTopoSave(db, simpleProjectionApp(false), {
401
- createdAt: '2026-04-03T12:00:00.000Z',
402
- })
403
- );
404
- const secondSave = unwrap(
405
- persistEstablishedTopoSave(db, simpleProjectionApp(true), {
406
- createdAt: '2026-04-03T12:05:00.000Z',
407
- })
408
- );
409
-
410
- expect(firstSave.id).not.toBe(secondSave.id);
411
- expect(countRows(db, 'topo_trails', firstSave.id)).toBe(1);
412
- expect(countRows(db, 'topo_trails', secondSave.id)).toBe(2);
413
- expect(readProjectedTrailIds(db, firstSave.id)).toEqual(['entity.add']);
414
- expect(readProjectedTrailIds(db, secondSave.id)).toEqual([
415
- 'entity.add',
416
- 'entity.list',
417
- ]);
418
- expect(requireStoredExport(db, firstSave.id).trailheadHash).not.toBe(
419
- requireStoredExport(db, secondSave.id).trailheadHash
420
- );
421
- });
422
- });
423
-
424
- test("pruning an unpinned save removes only that save's projected rows", () => {
425
- withProjectionDb((db) => {
426
- const app = exampleApp();
427
- const pinned = unwrap(
428
- persistEstablishedTopoSave(db, app, {
429
- createdAt: '2026-04-03T12:00:00.000Z',
430
- })
431
- );
432
- pinTopoSave(db, { name: 'before-auth', saveId: pinned.id });
433
-
434
- const disposable = unwrap(
435
- persistEstablishedTopoSave(db, app, {
436
- createdAt: '2026-04-03T12:05:00.000Z',
437
- })
438
- );
439
-
440
- expect(pruneUnpinnedTopoSaves(db, { keep: 0 })).toBe(1);
441
- expectProjectionCounts(db, pinned.id);
442
- expect(countRows(db, 'topo_trails', disposable.id)).toBe(0);
443
- expect(countRows(db, 'topo_crossings', disposable.id)).toBe(0);
444
- expect(countRows(db, 'topo_examples', disposable.id)).toBe(0);
445
- expect(countRows(db, 'topo_schemas', disposable.id)).toBe(0);
446
- });
447
- });
448
-
449
- test('upgrades a history-only topo schema to the projected topo schema', () => {
450
- withProjectionDb((db) => {
451
- seedHistoryOnlyTopoSchema(db);
452
- ensureTopoHistorySchema(db);
453
- expect(
454
- db
455
- .query<{ version: number }, []>(
456
- "SELECT version FROM meta_schema_versions WHERE subsystem = 'topo'"
457
- )
458
- .get()?.version
459
- ).toBe(3);
460
- expect(tableExists(db, 'topo_trails')).toBe(true);
461
- expect(tableExists(db, 'topo_crossings')).toBe(true);
462
- expect(tableExists(db, 'topo_examples')).toBe(true);
463
- expect(tableExists(db, 'topo_exports')).toBe(true);
464
- expect(tableExists(db, 'topo_schemas')).toBe(true);
465
- expect(countRows(db, 'topo_saves')).toBe(1);
466
- expect(countRows(db, 'topo_pins')).toBe(1);
467
- });
468
- });
469
- });