@ontrails/core 1.0.0-beta.13 → 1.0.0-beta.15

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 (287) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +19 -14
  4. package/dist/branded.d.ts +1 -1
  5. package/dist/branded.d.ts.map +1 -1
  6. package/dist/branded.js +1 -1
  7. package/dist/branded.js.map +1 -1
  8. package/dist/context.d.ts +13 -1
  9. package/dist/context.d.ts.map +1 -1
  10. package/dist/context.js +19 -3
  11. package/dist/context.js.map +1 -1
  12. package/dist/contour.d.ts +81 -0
  13. package/dist/contour.d.ts.map +1 -0
  14. package/dist/contour.js +176 -0
  15. package/dist/contour.js.map +1 -0
  16. package/dist/cross-schema.d.ts +18 -0
  17. package/dist/cross-schema.d.ts.map +1 -0
  18. package/dist/cross-schema.js +29 -0
  19. package/dist/cross-schema.js.map +1 -0
  20. package/dist/derive.d.ts +6 -0
  21. package/dist/derive.d.ts.map +1 -1
  22. package/dist/derive.js +29 -5
  23. package/dist/derive.js.map +1 -1
  24. package/dist/draft.d.ts +28 -0
  25. package/dist/draft.d.ts.map +1 -0
  26. package/dist/draft.js +157 -0
  27. package/dist/draft.js.map +1 -0
  28. package/dist/errors.d.ts +62 -4
  29. package/dist/errors.d.ts.map +1 -1
  30. package/dist/errors.js +44 -1
  31. package/dist/errors.js.map +1 -1
  32. package/dist/execute.d.ts +26 -9
  33. package/dist/execute.d.ts.map +1 -1
  34. package/dist/execute.js +441 -20
  35. package/dist/execute.js.map +1 -1
  36. package/dist/fire.d.ts +43 -0
  37. package/dist/fire.d.ts.map +1 -0
  38. package/dist/fire.js +185 -0
  39. package/dist/fire.js.map +1 -0
  40. package/dist/index.d.ts +33 -17
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +26 -12
  43. package/dist/index.js.map +1 -1
  44. package/dist/internal/cross-batch.d.ts +44 -0
  45. package/dist/internal/cross-batch.d.ts.map +1 -0
  46. package/dist/internal/cross-batch.js +58 -0
  47. package/dist/internal/cross-batch.js.map +1 -0
  48. package/dist/internal/fork-ctx.d.ts +41 -0
  49. package/dist/internal/fork-ctx.d.ts.map +1 -0
  50. package/dist/internal/fork-ctx.js +47 -0
  51. package/dist/internal/fork-ctx.js.map +1 -0
  52. package/dist/internal/signal-ref.d.ts +15 -0
  53. package/dist/internal/signal-ref.d.ts.map +1 -0
  54. package/dist/internal/signal-ref.js +41 -0
  55. package/dist/internal/signal-ref.js.map +1 -0
  56. package/dist/internal/topo-saves.d.ts +53 -0
  57. package/dist/internal/topo-saves.d.ts.map +1 -0
  58. package/dist/internal/topo-saves.js +449 -0
  59. package/dist/internal/topo-saves.js.map +1 -0
  60. package/dist/internal/topo-snapshots.d.ts +57 -0
  61. package/dist/internal/topo-snapshots.d.ts.map +1 -0
  62. package/dist/internal/topo-snapshots.js +372 -0
  63. package/dist/internal/topo-snapshots.js.map +1 -0
  64. package/dist/internal/topo-store-read.d.ts +70 -0
  65. package/dist/internal/topo-store-read.d.ts.map +1 -0
  66. package/dist/internal/topo-store-read.js +221 -0
  67. package/dist/internal/topo-store-read.js.map +1 -0
  68. package/dist/internal/topo-store.d.ts +26 -0
  69. package/dist/internal/topo-store.d.ts.map +1 -0
  70. package/dist/internal/topo-store.js +621 -0
  71. package/dist/internal/topo-store.js.map +1 -0
  72. package/dist/internal/tracing.d.ts +98 -0
  73. package/dist/internal/tracing.d.ts.map +1 -0
  74. package/dist/internal/tracing.js +102 -0
  75. package/dist/internal/tracing.js.map +1 -0
  76. package/dist/internal/trails-db.d.ts +16 -0
  77. package/dist/internal/trails-db.d.ts.map +1 -0
  78. package/dist/internal/trails-db.js +118 -0
  79. package/dist/internal/trails-db.js.map +1 -0
  80. package/dist/internal/zod-wrappers.d.ts +40 -0
  81. package/dist/internal/zod-wrappers.d.ts.map +1 -0
  82. package/dist/internal/zod-wrappers.js +59 -0
  83. package/dist/internal/zod-wrappers.js.map +1 -0
  84. package/dist/layer.d.ts +22 -5
  85. package/dist/layer.d.ts.map +1 -1
  86. package/dist/layer.js +20 -3
  87. package/dist/layer.js.map +1 -1
  88. package/dist/path-security.d.ts +1 -1
  89. package/dist/path-security.d.ts.map +1 -1
  90. package/dist/path-security.js +1 -1
  91. package/dist/path-security.js.map +1 -1
  92. package/dist/resilience.d.ts +1 -1
  93. package/dist/resilience.d.ts.map +1 -1
  94. package/dist/resilience.js +3 -3
  95. package/dist/resilience.js.map +1 -1
  96. package/dist/resource-config.d.ts +22 -0
  97. package/dist/resource-config.d.ts.map +1 -0
  98. package/dist/resource-config.js +210 -0
  99. package/dist/resource-config.js.map +1 -0
  100. package/dist/resource.d.ts +74 -0
  101. package/dist/resource.d.ts.map +1 -0
  102. package/dist/resource.js +61 -0
  103. package/dist/resource.js.map +1 -0
  104. package/dist/run.d.ts +4 -3
  105. package/dist/run.d.ts.map +1 -1
  106. package/dist/run.js +5 -4
  107. package/dist/run.js.map +1 -1
  108. package/dist/serialization.d.ts.map +1 -1
  109. package/dist/serialization.js +2 -1
  110. package/dist/serialization.js.map +1 -1
  111. package/dist/store/accessor-protocol.d.ts +51 -0
  112. package/dist/store/accessor-protocol.d.ts.map +1 -0
  113. package/dist/store/accessor-protocol.js +2 -0
  114. package/dist/store/accessor-protocol.js.map +1 -0
  115. package/dist/store/index.d.ts +2 -0
  116. package/dist/store/index.d.ts.map +1 -0
  117. package/dist/store/index.js +2 -0
  118. package/dist/store/index.js.map +1 -0
  119. package/dist/surface-filter.d.ts +13 -0
  120. package/dist/surface-filter.d.ts.map +1 -0
  121. package/dist/surface-filter.js +76 -0
  122. package/dist/surface-filter.js.map +1 -0
  123. package/dist/topo-store.d.ts +62 -0
  124. package/dist/topo-store.d.ts.map +1 -0
  125. package/dist/topo-store.js +319 -0
  126. package/dist/topo-store.js.map +1 -0
  127. package/dist/topo.d.ts +23 -9
  128. package/dist/topo.d.ts.map +1 -1
  129. package/dist/topo.js +175 -46
  130. package/dist/topo.js.map +1 -1
  131. package/dist/trail.d.ts +86 -19
  132. package/dist/trail.d.ts.map +1 -1
  133. package/dist/trail.js +59 -4
  134. package/dist/trail.js.map +1 -1
  135. package/dist/trails/derive-trail.d.ts +66 -0
  136. package/dist/trails/derive-trail.d.ts.map +1 -0
  137. package/dist/trails/derive-trail.js +378 -0
  138. package/dist/trails/derive-trail.js.map +1 -0
  139. package/dist/trails/index.d.ts +5 -0
  140. package/dist/trails/index.d.ts.map +1 -0
  141. package/dist/trails/index.js +3 -0
  142. package/dist/trails/index.js.map +1 -0
  143. package/dist/trails/ingest.d.ts +24 -0
  144. package/dist/trails/ingest.d.ts.map +1 -0
  145. package/dist/trails/ingest.js +63 -0
  146. package/dist/trails/ingest.js.map +1 -0
  147. package/dist/transport-error-map.d.ts +106 -0
  148. package/dist/transport-error-map.d.ts.map +1 -0
  149. package/dist/transport-error-map.js +24 -0
  150. package/dist/transport-error-map.js.map +1 -0
  151. package/dist/type-checks.test-d.d.ts +71 -0
  152. package/dist/type-checks.test-d.d.ts.map +1 -0
  153. package/dist/type-checks.test-d.js +12 -0
  154. package/dist/type-checks.test-d.js.map +1 -0
  155. package/dist/type-utils.d.ts +11 -2
  156. package/dist/type-utils.d.ts.map +1 -1
  157. package/dist/type-utils.js.map +1 -1
  158. package/dist/types.d.ts +113 -7
  159. package/dist/types.d.ts.map +1 -1
  160. package/dist/types.js.map +1 -1
  161. package/dist/validate-established-topo.d.ts +76 -0
  162. package/dist/validate-established-topo.d.ts.map +1 -0
  163. package/dist/validate-established-topo.js +43 -0
  164. package/dist/validate-established-topo.js.map +1 -0
  165. package/dist/validate-topo.d.ts.map +1 -1
  166. package/dist/validate-topo.js +27 -8
  167. package/dist/validate-topo.js.map +1 -1
  168. package/dist/workspace.d.ts +1 -1
  169. package/dist/workspace.d.ts.map +1 -1
  170. package/dist/workspace.js +1 -1
  171. package/dist/workspace.js.map +1 -1
  172. package/package.json +10 -1
  173. package/src/__tests__/branded.test.ts +5 -5
  174. package/src/__tests__/context.test.ts +3 -3
  175. package/src/__tests__/contour.test.ts +263 -0
  176. package/src/__tests__/derive-trail.test.ts +668 -0
  177. package/src/__tests__/derive.test.ts +58 -1
  178. package/src/__tests__/errors.test.ts +118 -0
  179. package/src/__tests__/execute.test.ts +1179 -141
  180. package/src/__tests__/fire.test.ts +1056 -0
  181. package/src/__tests__/fork-ctx.test.ts +83 -0
  182. package/src/__tests__/ingest.test.ts +191 -0
  183. package/src/__tests__/{gate.test.ts → layer.test.ts} +21 -21
  184. package/src/__tests__/path-security.test.ts +7 -7
  185. package/src/__tests__/resilience.test.ts +8 -8
  186. package/src/__tests__/resource.test.ts +204 -0
  187. package/src/__tests__/run.test.ts +13 -13
  188. package/src/__tests__/service-config.test.ts +28 -32
  189. package/src/__tests__/surface-filter.test.ts +196 -0
  190. package/src/__tests__/topo-store-read.test.ts +256 -0
  191. package/src/__tests__/topo-store.test.ts +869 -0
  192. package/src/__tests__/topo.test.ts +350 -54
  193. package/src/__tests__/trail.test.ts +189 -24
  194. package/src/__tests__/trails-db.test.ts +198 -0
  195. package/src/__tests__/transport-error-map.test.ts +82 -0
  196. package/src/__tests__/type-utils.test.ts +29 -1
  197. package/src/__tests__/validate-topo.test.ts +305 -16
  198. package/src/__tests__/workspace.test.ts +6 -6
  199. package/src/__tests__/zod-wrappers.test.ts +62 -0
  200. package/src/branded.ts +1 -1
  201. package/src/context.ts +24 -4
  202. package/src/contour.ts +344 -0
  203. package/src/cross-schema.ts +36 -0
  204. package/src/derive.ts +39 -8
  205. package/src/draft.ts +338 -0
  206. package/src/errors.ts +62 -18
  207. package/src/execute.ts +840 -29
  208. package/src/fire.ts +295 -0
  209. package/src/index.ts +142 -26
  210. package/src/internal/cross-batch.ts +77 -0
  211. package/src/internal/fork-ctx.ts +69 -0
  212. package/src/internal/signal-ref.ts +87 -0
  213. package/src/internal/topo-snapshots.ts +517 -0
  214. package/src/internal/topo-store-read.ts +481 -0
  215. package/src/internal/topo-store.ts +1192 -0
  216. package/src/internal/tracing.ts +187 -0
  217. package/src/internal/trails-db.ts +187 -0
  218. package/src/internal/zod-wrappers.ts +78 -0
  219. package/src/layer.ts +50 -0
  220. package/src/path-security.ts +1 -1
  221. package/src/resilience.ts +3 -3
  222. package/src/{provision-config.ts → resource-config.ts} +109 -123
  223. package/src/resource.ts +155 -0
  224. package/src/run.ts +5 -4
  225. package/src/serialization.ts +2 -0
  226. package/src/store/accessor-protocol.ts +56 -0
  227. package/src/store/index.ts +4 -0
  228. package/src/surface-filter.ts +182 -0
  229. package/src/topo-store.ts +510 -0
  230. package/src/topo.ts +328 -57
  231. package/src/trail.ts +199 -31
  232. package/src/trails/derive-trail.ts +842 -0
  233. package/src/trails/index.ts +9 -0
  234. package/src/trails/ingest.ts +146 -0
  235. package/src/transport-error-map.ts +56 -0
  236. package/src/type-checks.test-d.ts +106 -0
  237. package/src/type-utils.ts +17 -2
  238. package/src/types.ts +148 -11
  239. package/src/validate-established-topo.ts +63 -0
  240. package/src/validate-topo.ts +37 -9
  241. package/src/workspace.ts +1 -1
  242. package/tsconfig.tests.json +10 -0
  243. package/tsconfig.tsbuildinfo +1 -1
  244. package/dist/adapters.d.ts +0 -39
  245. package/dist/adapters.d.ts.map +0 -1
  246. package/dist/adapters.js +0 -2
  247. package/dist/adapters.js.map +0 -1
  248. package/dist/dispatch.d.ts +0 -27
  249. package/dist/dispatch.d.ts.map +0 -1
  250. package/dist/dispatch.js +0 -34
  251. package/dist/dispatch.js.map +0 -1
  252. package/dist/event.d.ts +0 -8
  253. package/dist/event.d.ts.map +0 -1
  254. package/dist/event.js +0 -7
  255. package/dist/event.js.map +0 -1
  256. package/dist/gate.d.ts +0 -17
  257. package/dist/gate.d.ts.map +0 -1
  258. package/dist/gate.js +0 -21
  259. package/dist/gate.js.map +0 -1
  260. package/dist/health.d.ts +0 -18
  261. package/dist/health.d.ts.map +0 -1
  262. package/dist/health.js +0 -5
  263. package/dist/health.js.map +0 -1
  264. package/dist/job.d.ts +0 -24
  265. package/dist/job.d.ts.map +0 -1
  266. package/dist/job.js +0 -17
  267. package/dist/job.js.map +0 -1
  268. package/dist/provision-config.d.ts +0 -22
  269. package/dist/provision-config.d.ts.map +0 -1
  270. package/dist/provision-config.js +0 -210
  271. package/dist/provision-config.js.map +0 -1
  272. package/dist/provision.d.ts +0 -71
  273. package/dist/provision.d.ts.map +0 -1
  274. package/dist/provision.js +0 -56
  275. package/dist/provision.js.map +0 -1
  276. package/dist/service-config.d.ts +0 -22
  277. package/dist/service-config.d.ts.map +0 -1
  278. package/dist/service-config.js +0 -210
  279. package/dist/service-config.js.map +0 -1
  280. package/dist/service.d.ts +0 -71
  281. package/dist/service.d.ts.map +0 -1
  282. package/dist/service.js +0 -56
  283. package/dist/service.js.map +0 -1
  284. package/src/__tests__/service.test.ts +0 -197
  285. package/src/event.ts +0 -15
  286. package/src/gate.ts +0 -44
  287. package/src/provision.ts +0 -148
@@ -0,0 +1,481 @@
1
+ import type { Database, SQLQueryBindings } from 'bun:sqlite';
2
+
3
+ import { ValidationError } from '../errors.js';
4
+ import type {
5
+ ListTopoSnapshotsOptions,
6
+ TopoSnapshot,
7
+ } from './topo-snapshots.js';
8
+ import {
9
+ listTopoSnapshots,
10
+ readPinnedTopoSnapshot,
11
+ readTopoSnapshot,
12
+ } from './topo-snapshots.js';
13
+ import type { StoredTopoExport } from './topo-store.js';
14
+ import { getStoredTopoExport } from './topo-store.js';
15
+
16
+ export interface TopoStoreRef {
17
+ readonly pin?: string;
18
+ readonly snapshotId?: string;
19
+ }
20
+
21
+ export interface TopoStoreTrailRecord {
22
+ readonly description: string | null;
23
+ readonly exampleCount: number;
24
+ readonly hasExamples: boolean;
25
+ readonly hasOutput: boolean;
26
+ readonly id: string;
27
+ readonly idempotent: boolean;
28
+ readonly intent: 'destroy' | 'read' | 'write';
29
+ readonly kind: 'trail';
30
+ readonly meta: Readonly<Record<string, unknown>> | null;
31
+ readonly pattern: string | null;
32
+ readonly safety: '-' | 'destroy' | 'read' | 'write';
33
+ readonly snapshotId: string;
34
+ }
35
+
36
+ export interface TopoStoreExampleRecord {
37
+ readonly description: string | null;
38
+ readonly error: string | null;
39
+ readonly expected: unknown;
40
+ readonly input: unknown;
41
+ readonly name: string;
42
+ readonly ordinal: number;
43
+ }
44
+
45
+ export interface TopoStoreTrailDetailRecord extends TopoStoreTrailRecord {
46
+ readonly crosses: readonly string[];
47
+ readonly detours:
48
+ | readonly { readonly on: string; readonly maxAttempts: number }[]
49
+ | null;
50
+ readonly examples: readonly TopoStoreExampleRecord[];
51
+ readonly resources: readonly string[];
52
+ }
53
+
54
+ export interface TopoStoreResourceRecord {
55
+ readonly description: string | null;
56
+ readonly hasHealth: boolean;
57
+ readonly hasMock: boolean;
58
+ readonly health: 'available' | 'none';
59
+ readonly id: string;
60
+ readonly kind: 'resource';
61
+ readonly lifetime: 'singleton';
62
+ readonly snapshotId: string;
63
+ readonly usedBy: readonly string[];
64
+ }
65
+
66
+ export interface TopoStoreExportRecord extends StoredTopoExport {
67
+ readonly snapshot: TopoSnapshot;
68
+ }
69
+
70
+ interface TopoTrailRow {
71
+ readonly description: string | null;
72
+ readonly example_count: number;
73
+ readonly has_examples: number;
74
+ readonly has_output: number;
75
+ readonly id: string;
76
+ readonly idempotent: number;
77
+ readonly intent: string | null;
78
+ readonly meta: string | null;
79
+ readonly pattern: string | null;
80
+ readonly snapshot_id: string;
81
+ }
82
+
83
+ interface TopoCrossingRow {
84
+ readonly target_id: string;
85
+ }
86
+
87
+ interface TopoTrailResourceRow {
88
+ readonly resource_id: string;
89
+ }
90
+
91
+ interface TopoExampleRow {
92
+ readonly description: string | null;
93
+ readonly error: string | null;
94
+ readonly expected: string | null;
95
+ readonly input: string;
96
+ readonly name: string;
97
+ readonly ordinal: number;
98
+ }
99
+
100
+ interface TopoResourceRow {
101
+ readonly has_health: number;
102
+ readonly has_mock: number;
103
+ readonly id: string;
104
+ readonly snapshot_id: string;
105
+ }
106
+
107
+ interface StoredSurfaceMapEntry {
108
+ readonly description?: string;
109
+ readonly detours?: readonly {
110
+ readonly on: string;
111
+ readonly maxAttempts: number;
112
+ }[];
113
+ readonly healthcheck?: boolean;
114
+ readonly id: string;
115
+ readonly kind: 'contour' | 'resource' | 'signal' | 'trail';
116
+ }
117
+
118
+ interface StoredSurfaceMap {
119
+ readonly entries: readonly StoredSurfaceMapEntry[];
120
+ }
121
+
122
+ const ensureSingleRefSelector = (ref?: TopoStoreRef): void => {
123
+ if (ref?.pin !== undefined && ref.snapshotId !== undefined) {
124
+ throw new ValidationError(
125
+ 'Topo store references may use pin or snapshotId, not both'
126
+ );
127
+ }
128
+ };
129
+
130
+ const normalizeIntent = (
131
+ intent: string | null
132
+ ): TopoStoreTrailRecord['intent'] => {
133
+ if (intent === 'destroy' || intent === 'read') {
134
+ return intent;
135
+ }
136
+ return 'write';
137
+ };
138
+
139
+ const safetyForIntent = (
140
+ intent: TopoStoreTrailRecord['intent']
141
+ ): TopoStoreTrailRecord['safety'] => {
142
+ if (intent === 'destroy') {
143
+ return 'destroy';
144
+ }
145
+ if (intent === 'read') {
146
+ return 'read';
147
+ }
148
+ return 'write';
149
+ };
150
+
151
+ const parseMeta = (
152
+ value: string | null
153
+ ): Readonly<Record<string, unknown>> | null =>
154
+ value === null
155
+ ? null
156
+ : (JSON.parse(value) as Readonly<Record<string, unknown>>);
157
+
158
+ const parseJson = (value: string): unknown => JSON.parse(value) as unknown;
159
+
160
+ const readSnapshotRef = (
161
+ db: Database,
162
+ ref?: TopoStoreRef
163
+ ): TopoSnapshot | undefined => {
164
+ ensureSingleRefSelector(ref);
165
+
166
+ if (ref?.snapshotId !== undefined) {
167
+ return readTopoSnapshot(db, ref.snapshotId);
168
+ }
169
+
170
+ if (ref?.pin !== undefined) {
171
+ return readPinnedTopoSnapshot(db, ref.pin);
172
+ }
173
+
174
+ return listTopoSnapshots(db, { limit: 1 })[0];
175
+ };
176
+
177
+ const readStoredEntry = (
178
+ db: Database,
179
+ snapshotId: string,
180
+ kind: StoredSurfaceMapEntry['kind'],
181
+ id: string
182
+ ): StoredSurfaceMapEntry | undefined => {
183
+ const stored = getStoredTopoExport(db, snapshotId);
184
+ if (stored === undefined) {
185
+ return undefined;
186
+ }
187
+
188
+ const map = JSON.parse(stored.surfaceMapJson) as StoredSurfaceMap;
189
+ return map.entries.find((entry) => entry.id === id && entry.kind === kind);
190
+ };
191
+
192
+ const mapTrailRow = (row: TopoTrailRow): TopoStoreTrailRecord => {
193
+ const intent = normalizeIntent(row.intent);
194
+
195
+ return {
196
+ description: row.description,
197
+ exampleCount: row.example_count,
198
+ hasExamples: row.has_examples === 1,
199
+ hasOutput: row.has_output === 1,
200
+ id: row.id,
201
+ idempotent: row.idempotent === 1,
202
+ intent,
203
+ kind: 'trail',
204
+ meta: parseMeta(row.meta),
205
+ pattern: row.pattern,
206
+ safety: safetyForIntent(intent),
207
+ snapshotId: row.snapshot_id,
208
+ };
209
+ };
210
+
211
+ const readTrailCrossings = (
212
+ db: Database,
213
+ snapshotId: string,
214
+ trailId: string
215
+ ): readonly string[] =>
216
+ db
217
+ .query<TopoCrossingRow, [string, string]>(
218
+ `SELECT target_id
219
+ FROM topo_crossings
220
+ WHERE snapshot_id = ? AND source_id = ?
221
+ ORDER BY target_id ASC`
222
+ )
223
+ .all(snapshotId, trailId)
224
+ .map((row) => row.target_id);
225
+
226
+ const readTrailResourceIds = (
227
+ db: Database,
228
+ snapshotId: string,
229
+ trailId: string
230
+ ): readonly string[] =>
231
+ db
232
+ .query<TopoTrailResourceRow, [string, string]>(
233
+ `SELECT resource_id
234
+ FROM topo_trail_resources
235
+ WHERE snapshot_id = ? AND trail_id = ?
236
+ ORDER BY resource_id ASC`
237
+ )
238
+ .all(snapshotId, trailId)
239
+ .map((row) => row.resource_id);
240
+
241
+ const readTrailExamples = (
242
+ db: Database,
243
+ snapshotId: string,
244
+ trailId: string
245
+ ): readonly TopoStoreExampleRecord[] =>
246
+ db
247
+ .query<TopoExampleRow, [string, string]>(
248
+ `SELECT ordinal, name, description, input, expected, error
249
+ FROM topo_examples
250
+ WHERE snapshot_id = ? AND trail_id = ?
251
+ ORDER BY ordinal ASC`
252
+ )
253
+ .all(snapshotId, trailId)
254
+ .map((row) => ({
255
+ description: row.description,
256
+ error: row.error,
257
+ expected: row.expected === null ? null : parseJson(row.expected),
258
+ input: parseJson(row.input),
259
+ name: row.name,
260
+ ordinal: row.ordinal,
261
+ }));
262
+
263
+ const readResourceUsage = (
264
+ db: Database,
265
+ snapshotId: string
266
+ ): ReadonlyMap<string, readonly string[]> => {
267
+ const rows = db
268
+ .query<{ resource_id: string; trail_id: string }, [string]>(
269
+ `SELECT resource_id, trail_id
270
+ FROM topo_trail_resources
271
+ WHERE snapshot_id = ?
272
+ ORDER BY resource_id ASC, trail_id ASC`
273
+ )
274
+ .all(snapshotId);
275
+
276
+ const usage = new Map<string, string[]>();
277
+ for (const row of rows) {
278
+ const trails = usage.get(row.resource_id) ?? [];
279
+ trails.push(row.trail_id);
280
+ usage.set(row.resource_id, trails);
281
+ }
282
+
283
+ return new Map(
284
+ [...usage.entries()].map(([id, trails]) => [id, trails] as const)
285
+ );
286
+ };
287
+
288
+ export const readTopoStoreSnapshot = (
289
+ db: Database,
290
+ ref?: TopoStoreRef
291
+ ): TopoSnapshot | undefined => readSnapshotRef(db, ref);
292
+
293
+ export const listTopoStoreSnapshots = (
294
+ db: Database,
295
+ options?: ListTopoSnapshotsOptions
296
+ ): readonly TopoSnapshot[] => listTopoSnapshots(db, options);
297
+
298
+ export const getTopoStoreExport = (
299
+ db: Database,
300
+ ref?: TopoStoreRef
301
+ ): TopoStoreExportRecord | undefined => {
302
+ const snapshot = readSnapshotRef(db, ref);
303
+ if (snapshot === undefined) {
304
+ return undefined;
305
+ }
306
+
307
+ const stored = getStoredTopoExport(db, snapshot.id);
308
+ if (stored === undefined) {
309
+ return undefined;
310
+ }
311
+
312
+ return {
313
+ ...stored,
314
+ snapshot,
315
+ };
316
+ };
317
+
318
+ export const listTopoStoreTrails = (
319
+ db: Database,
320
+ options?: {
321
+ readonly intent?: TopoStoreTrailRecord['intent'];
322
+ readonly snapshot?: TopoStoreRef;
323
+ }
324
+ ): readonly TopoStoreTrailRecord[] => {
325
+ const snapshot = readSnapshotRef(db, options?.snapshot);
326
+ if (snapshot === undefined) {
327
+ return [];
328
+ }
329
+
330
+ const baseQuery = `SELECT id, intent, idempotent, has_output, has_examples, example_count, description, pattern, meta, snapshot_id
331
+ FROM topo_trails`;
332
+
333
+ let rows: TopoTrailRow[];
334
+ if (options?.intent === undefined) {
335
+ rows = db
336
+ .query<TopoTrailRow, [string]>(
337
+ `${baseQuery} WHERE snapshot_id = ? ORDER BY id ASC`
338
+ )
339
+ .all(snapshot.id);
340
+ } else if (options.intent === 'write') {
341
+ rows = db
342
+ .query<TopoTrailRow, [string]>(
343
+ `${baseQuery} WHERE snapshot_id = ? AND intent = 'write' ORDER BY id ASC`
344
+ )
345
+ .all(snapshot.id);
346
+ } else {
347
+ rows = db
348
+ .query<TopoTrailRow, [string, string]>(
349
+ `${baseQuery} WHERE snapshot_id = ? AND intent = ? ORDER BY id ASC`
350
+ )
351
+ .all(snapshot.id, options.intent);
352
+ }
353
+
354
+ return rows.map(mapTrailRow);
355
+ };
356
+
357
+ export const getTopoStoreTrail = (
358
+ db: Database,
359
+ trailId: string,
360
+ options?: { readonly snapshot?: TopoStoreRef }
361
+ ): TopoStoreTrailDetailRecord | undefined => {
362
+ const snapshot = readSnapshotRef(db, options?.snapshot);
363
+ if (snapshot === undefined) {
364
+ return undefined;
365
+ }
366
+
367
+ const row = db
368
+ .query<TopoTrailRow, [string, string]>(
369
+ `SELECT id, intent, idempotent, has_output, has_examples, example_count, description, pattern, meta, snapshot_id
370
+ FROM topo_trails
371
+ WHERE snapshot_id = ? AND id = ?
372
+ LIMIT 1`
373
+ )
374
+ .get(snapshot.id, trailId);
375
+
376
+ if (row === null || row === undefined) {
377
+ return undefined;
378
+ }
379
+
380
+ const storedEntry = readStoredEntry(db, snapshot.id, 'trail', trailId);
381
+
382
+ return {
383
+ ...mapTrailRow(row),
384
+ crosses: readTrailCrossings(db, snapshot.id, trailId),
385
+ detours: storedEntry?.detours ?? null,
386
+ examples: readTrailExamples(db, snapshot.id, trailId),
387
+ resources: readTrailResourceIds(db, snapshot.id, trailId),
388
+ };
389
+ };
390
+
391
+ const mapResourceRow = (
392
+ row: TopoResourceRow,
393
+ usedBy: readonly string[],
394
+ storedEntry?: StoredSurfaceMapEntry
395
+ ): TopoStoreResourceRecord => ({
396
+ description: storedEntry?.description ?? null,
397
+ hasHealth: row.has_health === 1,
398
+ hasMock: row.has_mock === 1,
399
+ health:
400
+ row.has_health === 1 || storedEntry?.healthcheck === true
401
+ ? 'available'
402
+ : 'none',
403
+ id: row.id,
404
+ kind: 'resource',
405
+ lifetime: 'singleton',
406
+ snapshotId: row.snapshot_id,
407
+ usedBy,
408
+ });
409
+
410
+ export const listTopoStoreResources = (
411
+ db: Database,
412
+ options?: { readonly snapshot?: TopoStoreRef }
413
+ ): readonly TopoStoreResourceRecord[] => {
414
+ const snapshot = readSnapshotRef(db, options?.snapshot);
415
+ if (snapshot === undefined) {
416
+ return [];
417
+ }
418
+
419
+ const usage = readResourceUsage(db, snapshot.id);
420
+ const rows = db
421
+ .query<TopoResourceRow, [string]>(
422
+ `SELECT id, has_mock, has_health, snapshot_id
423
+ FROM topo_resources
424
+ WHERE snapshot_id = ?
425
+ ORDER BY id ASC`
426
+ )
427
+ .all(snapshot.id);
428
+
429
+ const stored = getStoredTopoExport(db, snapshot.id);
430
+ const entries = stored
431
+ ? (JSON.parse(stored.surfaceMapJson) as StoredSurfaceMap).entries
432
+ : [];
433
+
434
+ return rows.map((row) =>
435
+ mapResourceRow(
436
+ row,
437
+ usage.get(row.id) ?? [],
438
+ entries.find((entry) => entry.id === row.id && entry.kind === 'resource')
439
+ )
440
+ );
441
+ };
442
+
443
+ export const getTopoStoreResource = (
444
+ db: Database,
445
+ resourceId: string,
446
+ options?: { readonly snapshot?: TopoStoreRef }
447
+ ): TopoStoreResourceRecord | undefined => {
448
+ const snapshot = readSnapshotRef(db, options?.snapshot);
449
+ if (snapshot === undefined) {
450
+ return undefined;
451
+ }
452
+
453
+ const row = db
454
+ .query<TopoResourceRow, [string, string]>(
455
+ `SELECT id, has_mock, has_health, snapshot_id
456
+ FROM topo_resources
457
+ WHERE snapshot_id = ? AND id = ?
458
+ LIMIT 1`
459
+ )
460
+ .get(snapshot.id, resourceId);
461
+
462
+ if (row === null || row === undefined) {
463
+ return undefined;
464
+ }
465
+
466
+ const usage = readResourceUsage(db, snapshot.id);
467
+ return mapResourceRow(
468
+ row,
469
+ usage.get(resourceId) ?? [],
470
+ readStoredEntry(db, snapshot.id, 'resource', resourceId)
471
+ );
472
+ };
473
+
474
+ export const queryTopoStore = <TRow extends Record<string, unknown>>(
475
+ db: Database,
476
+ sql: string,
477
+ bindings?: readonly SQLQueryBindings[]
478
+ ): readonly TRow[] =>
479
+ db
480
+ .query<TRow, SQLQueryBindings[]>(sql)
481
+ .all(...(bindings === undefined ? [] : [...bindings]));