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