@smithers-orchestrator/db 0.16.0

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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +43 -0
  3. package/src/JsonBounds.ts +6 -0
  4. package/src/SchemaRegistryEntry.ts +6 -0
  5. package/src/SqlMessageStorage.js +818 -0
  6. package/src/SqlMessageStorageEventHistoryQuery.ts +7 -0
  7. package/src/SqliteWriteRetryOptions.ts +7 -0
  8. package/src/adapter/AlertRow.ts +29 -0
  9. package/src/adapter/AlertSeverity.ts +2 -0
  10. package/src/adapter/AlertStatus.ts +2 -0
  11. package/src/adapter/ApprovalRow.ts +13 -0
  12. package/src/adapter/AttemptRow.ts +17 -0
  13. package/src/adapter/CacheRow.ts +12 -0
  14. package/src/adapter/DB_ALERT_ALLOWED_SEVERITIES.js +5 -0
  15. package/src/adapter/DB_ALERT_ALLOWED_STATUSES.js +6 -0
  16. package/src/adapter/DB_ALERT_ID_MAX_LENGTH.js +1 -0
  17. package/src/adapter/DB_ALERT_MESSAGE_MAX_LENGTH.js +1 -0
  18. package/src/adapter/DB_ALERT_POLICY_NAME_MAX_LENGTH.js +1 -0
  19. package/src/adapter/DB_RUN_ALLOWED_STATUSES.js +10 -0
  20. package/src/adapter/DB_RUN_ID_MAX_LENGTH.js +1 -0
  21. package/src/adapter/DB_RUN_WORKFLOW_NAME_MAX_LENGTH.js +1 -0
  22. package/src/adapter/EventHistoryQuery.ts +7 -0
  23. package/src/adapter/HumanRequestRow.ts +19 -0
  24. package/src/adapter/NodeDiffCacheRow.ts +9 -0
  25. package/src/adapter/NodeRow.ts +10 -0
  26. package/src/adapter/PendingHumanRequestRow.ts +7 -0
  27. package/src/adapter/RunAncestryRow.ts +5 -0
  28. package/src/adapter/RunRow.ts +21 -0
  29. package/src/adapter/SignalQuery.ts +6 -0
  30. package/src/adapter/SignalRow.ts +9 -0
  31. package/src/adapter/SmithersDb.js +2236 -0
  32. package/src/adapter/StaleRunRecord.ts +7 -0
  33. package/src/adapter/index.js +27 -0
  34. package/src/adapter.js +2359 -0
  35. package/src/assertJsonPayloadWithinBounds.js +94 -0
  36. package/src/assertMaxBytes.js +23 -0
  37. package/src/assertMaxJsonDepth.js +40 -0
  38. package/src/assertMaxStringLength.js +16 -0
  39. package/src/assertOptionalArrayMaxLength.js +16 -0
  40. package/src/assertOptionalStringMaxLength.js +11 -0
  41. package/src/assertPositiveFiniteInteger.js +14 -0
  42. package/src/assertPositiveFiniteNumber.js +12 -0
  43. package/src/buildHumanRequestId.js +9 -0
  44. package/src/cache/nodeDiffCache.js +124 -0
  45. package/src/ensure.js +18 -0
  46. package/src/ensureSqlMessageStorage.js +11 -0
  47. package/src/ensureSqlMessageStorageEffect.js +12 -0
  48. package/src/frame-codec/FRAME_KEYFRAME_INTERVAL.js +1 -0
  49. package/src/frame-codec/FrameDelta.ts +6 -0
  50. package/src/frame-codec/FrameDeltaOp.ts +20 -0
  51. package/src/frame-codec/FrameEncoding.ts +1 -0
  52. package/src/frame-codec/JsonPath.ts +3 -0
  53. package/src/frame-codec/JsonPathSegment.ts +1 -0
  54. package/src/frame-codec/applyFrameDelta.js +143 -0
  55. package/src/frame-codec/applyFrameDeltaJson.js +10 -0
  56. package/src/frame-codec/encodeFrameDelta.js +247 -0
  57. package/src/frame-codec/index.js +15 -0
  58. package/src/frame-codec/normalizeFrameEncoding.js +13 -0
  59. package/src/frame-codec/parseFrameDelta.js +27 -0
  60. package/src/frame-codec/serializeFrameDelta.js +9 -0
  61. package/src/frame-codec.js +409 -0
  62. package/src/getSqlMessageStorage.js +11 -0
  63. package/src/index.d.ts +5203 -0
  64. package/src/index.js +20 -0
  65. package/src/input-bounds.js +12 -0
  66. package/src/input.js +17 -0
  67. package/src/internal-schema/index.js +19 -0
  68. package/src/internal-schema/smithersAlerts.js +27 -0
  69. package/src/internal-schema/smithersApprovals.js +18 -0
  70. package/src/internal-schema/smithersAttempts.js +20 -0
  71. package/src/internal-schema/smithersCache.js +13 -0
  72. package/src/internal-schema/smithersCron.js +11 -0
  73. package/src/internal-schema/smithersEvents.js +10 -0
  74. package/src/internal-schema/smithersFrames.js +14 -0
  75. package/src/internal-schema/smithersHumanRequests.js +17 -0
  76. package/src/internal-schema/smithersNodeDiffs.js +12 -0
  77. package/src/internal-schema/smithersNodes.js +13 -0
  78. package/src/internal-schema/smithersRalph.js +10 -0
  79. package/src/internal-schema/smithersRuns.js +22 -0
  80. package/src/internal-schema/smithersSandboxes.js +16 -0
  81. package/src/internal-schema/smithersSignals.js +12 -0
  82. package/src/internal-schema/smithersTimeTravelAudit.js +12 -0
  83. package/src/internal-schema/smithersToolCalls.js +19 -0
  84. package/src/internal-schema/smithersVectors.js +12 -0
  85. package/src/internal-schema.js +245 -0
  86. package/src/isRetryableSqliteWriteError.js +53 -0
  87. package/src/loadInputEffect.js +28 -0
  88. package/src/loadOutputsEffect.js +87 -0
  89. package/src/output/OutputKey.ts +1 -0
  90. package/src/output/buildKeyWhere.js +17 -0
  91. package/src/output/buildOutputRow.js +34 -0
  92. package/src/output/describeSchemaShape.js +70 -0
  93. package/src/output/getAgentOutputSchema.js +13 -0
  94. package/src/output/getKeyColumns.js +19 -0
  95. package/src/output/index.js +14 -0
  96. package/src/output/selectOutputRowEffect.js +30 -0
  97. package/src/output/stripAutoColumns.js +10 -0
  98. package/src/output/upsertOutputRowEffect.js +38 -0
  99. package/src/output/validateExistingOutput.js +17 -0
  100. package/src/output/validateOutput.js +17 -0
  101. package/src/output-schema-descriptor.js +163 -0
  102. package/src/output.js +240 -0
  103. package/src/react-output.js +10 -0
  104. package/src/runState/ComputeRunStateOptions.ts +4 -0
  105. package/src/runState/DeriveRunStateInput.ts +10 -0
  106. package/src/runState/RUN_STATE_HEARTBEAT_STALE_MS.js +1 -0
  107. package/src/runState/ReasonBlocked.ts +10 -0
  108. package/src/runState/ReasonUnhealthy.ts +6 -0
  109. package/src/runState/RunState.ts +12 -0
  110. package/src/runState/RunStateView.ts +11 -0
  111. package/src/runState/computeRunState.js +22 -0
  112. package/src/runState/computeRunStateFromRow.js +102 -0
  113. package/src/runState/deriveRunState.js +109 -0
  114. package/src/runState/parseEventMeta.js +18 -0
  115. package/src/runState/parseTimerMeta.js +16 -0
  116. package/src/runState-types.ts +23 -0
  117. package/src/runState.js +7 -0
  118. package/src/schema-signature.js +22 -0
  119. package/src/snapshot.js +125 -0
  120. package/src/sql-message-storage.js +839 -0
  121. package/src/storage/InMemoryStorage.js +484 -0
  122. package/src/storage/StorageService.js +7 -0
  123. package/src/storage/StorageServiceShape.ts +122 -0
  124. package/src/storage/StorageServiceTypes.ts +150 -0
  125. package/src/unwrapZodType.js +17 -0
  126. package/src/utils/camelToSnake.js +6 -0
  127. package/src/withSqliteWriteRetryEffect.js +110 -0
  128. package/src/write-retry.js +49 -0
  129. package/src/zodToCreateTableSQL.js +41 -0
  130. package/src/zodToTable.js +60 -0
@@ -0,0 +1,409 @@
1
+ import { canonicalizeXml, parseXmlJson } from "@smithers-orchestrator/graph/utils/xml";
2
+ /** @typedef {import("./frame-codec/FrameDelta.ts").FrameDelta} FrameDelta */
3
+ /** @typedef {import("./frame-codec/FrameDeltaOp.ts").FrameDeltaOp} FrameDeltaOp */
4
+ /** @typedef {import("./frame-codec/FrameEncoding.ts").FrameEncoding} FrameEncoding */
5
+ /** @typedef {import("./frame-codec/JsonPath.ts").JsonPath} JsonPath */
6
+ /** @typedef {import("./frame-codec/JsonPathSegment.ts").JsonPathSegment} JsonPathSegment */
7
+
8
+ export const FRAME_KEYFRAME_INTERVAL = 50;
9
+ const FRAME_DELTA_VERSION = 1;
10
+ /**
11
+ * @param {unknown} value
12
+ * @returns {FrameEncoding}
13
+ */
14
+ export function normalizeFrameEncoding(value) {
15
+ if (value === "delta")
16
+ return "delta";
17
+ if (value === "keyframe")
18
+ return "keyframe";
19
+ return "full";
20
+ }
21
+ /**
22
+ * @param {string} deltaJson
23
+ * @returns {FrameDelta}
24
+ */
25
+ export function parseFrameDelta(deltaJson) {
26
+ const parsed = JSON.parse(deltaJson);
27
+ if (!isRecord(parsed)) {
28
+ throw new Error("Invalid frame delta payload (not an object)");
29
+ }
30
+ if (parsed.version !== FRAME_DELTA_VERSION) {
31
+ throw new Error(`Unsupported frame delta version: ${String(parsed.version)}`);
32
+ }
33
+ if (!Array.isArray(parsed.ops)) {
34
+ throw new Error("Invalid frame delta payload (missing ops array)");
35
+ }
36
+ return parsed;
37
+ }
38
+ /**
39
+ * @param {FrameDelta} delta
40
+ * @returns {string}
41
+ */
42
+ export function serializeFrameDelta(delta) {
43
+ return JSON.stringify(delta);
44
+ }
45
+ /**
46
+ * @param {string} previousXmlJson
47
+ * @param {string} nextXmlJson
48
+ * @returns {FrameDelta}
49
+ */
50
+ export function encodeFrameDelta(previousXmlJson, nextXmlJson) {
51
+ const prev = parseXmlJson(previousXmlJson);
52
+ const next = parseXmlJson(nextXmlJson);
53
+ const ops = [];
54
+ diffValues(prev, next, [], ops, null);
55
+ return {
56
+ version: FRAME_DELTA_VERSION,
57
+ ops,
58
+ };
59
+ }
60
+ /**
61
+ * @param {string} previousXmlJson
62
+ * @param {FrameDelta} delta
63
+ * @returns {string}
64
+ */
65
+ export function applyFrameDelta(previousXmlJson, delta) {
66
+ const root = cloneValue(parseXmlJson(previousXmlJson));
67
+ const next = applyOps(root, delta.ops);
68
+ return canonicalizeXml(next);
69
+ }
70
+ /**
71
+ * @param {string} previousXmlJson
72
+ * @param {string} deltaJson
73
+ * @returns {string}
74
+ */
75
+ export function applyFrameDeltaJson(previousXmlJson, deltaJson) {
76
+ return applyFrameDelta(previousXmlJson, parseFrameDelta(deltaJson));
77
+ }
78
+ /**
79
+ * @param {unknown} prev
80
+ * @param {unknown} next
81
+ * @param {JsonPath} path
82
+ * @param {FrameDeltaOp[]} ops
83
+ * @param {string | null} currentNodeId
84
+ */
85
+ function diffValues(prev, next, path, ops, currentNodeId) {
86
+ if (deepEqual(prev, next))
87
+ return;
88
+ if (prev === undefined && next !== undefined) {
89
+ pushSet(ops, path, next, currentNodeId);
90
+ return;
91
+ }
92
+ if (next === undefined) {
93
+ pushRemove(ops, path, currentNodeId);
94
+ return;
95
+ }
96
+ const prevIsObj = isRecord(prev);
97
+ const nextIsObj = isRecord(next);
98
+ if (Array.isArray(prev) && Array.isArray(next)) {
99
+ diffArrays(prev, next, path, ops, currentNodeId);
100
+ return;
101
+ }
102
+ if (prevIsObj && nextIsObj) {
103
+ const nodeId = inferNodeId(next, inferNodeId(prev, currentNodeId));
104
+ diffObjects(prev, next, path, ops, nodeId);
105
+ return;
106
+ }
107
+ pushSet(ops, path, next, currentNodeId);
108
+ }
109
+ /**
110
+ * @param {Record<string, unknown>} prev
111
+ * @param {Record<string, unknown>} next
112
+ * @param {JsonPath} path
113
+ * @param {FrameDeltaOp[]} ops
114
+ * @param {string | null} currentNodeId
115
+ */
116
+ function diffObjects(prev, next, path, ops, currentNodeId) {
117
+ const keys = Array.from(new Set([...Object.keys(prev), ...Object.keys(next)])).sort();
118
+ for (const key of keys) {
119
+ const hasPrev = Object.prototype.hasOwnProperty.call(prev, key);
120
+ const hasNext = Object.prototype.hasOwnProperty.call(next, key);
121
+ const nextPath = [...path, key];
122
+ if (!hasNext && hasPrev) {
123
+ pushRemove(ops, nextPath, currentNodeId);
124
+ continue;
125
+ }
126
+ if (hasNext && !hasPrev) {
127
+ pushSet(ops, nextPath, next[key], currentNodeId);
128
+ continue;
129
+ }
130
+ diffValues(prev[key], next[key], nextPath, ops, currentNodeId);
131
+ }
132
+ }
133
+ /**
134
+ * @param {unknown[]} prev
135
+ * @param {unknown[]} next
136
+ * @param {JsonPath} path
137
+ * @param {FrameDeltaOp[]} ops
138
+ * @param {string | null} currentNodeId
139
+ */
140
+ function diffArrays(prev, next, path, ops, currentNodeId) {
141
+ let start = 0;
142
+ while (start < prev.length && start < next.length && deepEqual(prev[start], next[start])) {
143
+ start += 1;
144
+ }
145
+ let prevEnd = prev.length - 1;
146
+ let nextEnd = next.length - 1;
147
+ while (prevEnd >= start && nextEnd >= start && deepEqual(prev[prevEnd], next[nextEnd])) {
148
+ prevEnd -= 1;
149
+ nextEnd -= 1;
150
+ }
151
+ const prevCount = prevEnd - start + 1;
152
+ const nextCount = nextEnd - start + 1;
153
+ if (prevCount <= 0 && nextCount <= 0) {
154
+ return;
155
+ }
156
+ if (prevCount <= 0) {
157
+ for (let i = 0; i < nextCount; i += 1) {
158
+ pushInsert(ops, [...path, start + i], next[start + i], currentNodeId);
159
+ }
160
+ return;
161
+ }
162
+ if (nextCount <= 0) {
163
+ for (let i = prevEnd; i >= start; i -= 1) {
164
+ pushRemove(ops, [...path, i], currentNodeId);
165
+ }
166
+ return;
167
+ }
168
+ if (prevCount === nextCount) {
169
+ for (let i = 0; i < prevCount; i += 1) {
170
+ const prevValue = prev[start + i];
171
+ const nextValue = next[start + i];
172
+ const childNodeId = inferNodeId(nextValue, inferNodeId(prevValue, currentNodeId));
173
+ diffValues(prevValue, nextValue, [...path, start + i], ops, childNodeId);
174
+ }
175
+ return;
176
+ }
177
+ for (let i = prevEnd; i >= start; i -= 1) {
178
+ pushRemove(ops, [...path, i], currentNodeId);
179
+ }
180
+ for (let i = 0; i < nextCount; i += 1) {
181
+ pushInsert(ops, [...path, start + i], next[start + i], currentNodeId);
182
+ }
183
+ }
184
+ /**
185
+ * @param {FrameDeltaOp[]} ops
186
+ * @param {JsonPath} path
187
+ * @param {unknown} value
188
+ * @param {string | null} nodeId
189
+ */
190
+ function pushSet(ops, path, value, nodeId) {
191
+ const op = {
192
+ op: "set",
193
+ path,
194
+ value: cloneValue(value),
195
+ ...(nodeId ? { nodeId } : {}),
196
+ };
197
+ ops.push(op);
198
+ }
199
+ /**
200
+ * @param {FrameDeltaOp[]} ops
201
+ * @param {JsonPath} path
202
+ * @param {unknown} value
203
+ * @param {string | null} nodeId
204
+ */
205
+ function pushInsert(ops, path, value, nodeId) {
206
+ const op = {
207
+ op: "insert",
208
+ path,
209
+ value: cloneValue(value),
210
+ ...(nodeId ? { nodeId } : {}),
211
+ };
212
+ ops.push(op);
213
+ }
214
+ /**
215
+ * @param {FrameDeltaOp[]} ops
216
+ * @param {JsonPath} path
217
+ * @param {string | null} nodeId
218
+ */
219
+ function pushRemove(ops, path, nodeId) {
220
+ const op = {
221
+ op: "remove",
222
+ path,
223
+ ...(nodeId ? { nodeId } : {}),
224
+ };
225
+ ops.push(op);
226
+ }
227
+ /**
228
+ * @param {unknown} root
229
+ * @param {FrameDeltaOp[]} ops
230
+ * @returns {unknown}
231
+ */
232
+ function applyOps(root, ops) {
233
+ let current = root;
234
+ for (const op of ops) {
235
+ if (op.op === "set") {
236
+ current = setAtPath(current, op.path, op.value);
237
+ continue;
238
+ }
239
+ if (op.op === "insert") {
240
+ current = insertAtPath(current, op.path, op.value);
241
+ continue;
242
+ }
243
+ current = removeAtPath(current, op.path);
244
+ }
245
+ return current;
246
+ }
247
+ /**
248
+ * @param {unknown} root
249
+ * @param {JsonPath} path
250
+ * @param {unknown} value
251
+ * @returns {unknown}
252
+ */
253
+ function setAtPath(root, path, value) {
254
+ if (path.length === 0) {
255
+ return cloneValue(value);
256
+ }
257
+ const { parent, key } = getParentAndKey(root, path);
258
+ if (Array.isArray(parent)) {
259
+ if (typeof key !== "number") {
260
+ throw new Error("Invalid array set path");
261
+ }
262
+ parent[key] = cloneValue(value);
263
+ return root;
264
+ }
265
+ if (!isRecord(parent) || typeof key !== "string") {
266
+ throw new Error("Invalid object set path");
267
+ }
268
+ parent[key] = cloneValue(value);
269
+ return root;
270
+ }
271
+ /**
272
+ * @param {unknown} root
273
+ * @param {JsonPath} path
274
+ * @param {unknown} value
275
+ * @returns {unknown}
276
+ */
277
+ function insertAtPath(root, path, value) {
278
+ if (path.length === 0) {
279
+ return cloneValue(value);
280
+ }
281
+ const { parent, key } = getParentAndKey(root, path);
282
+ if (!Array.isArray(parent) || typeof key !== "number") {
283
+ throw new Error("Invalid insert path");
284
+ }
285
+ parent.splice(key, 0, cloneValue(value));
286
+ return root;
287
+ }
288
+ /**
289
+ * @param {unknown} root
290
+ * @param {JsonPath} path
291
+ * @returns {unknown}
292
+ */
293
+ function removeAtPath(root, path) {
294
+ if (path.length === 0) {
295
+ return null;
296
+ }
297
+ const { parent, key } = getParentAndKey(root, path);
298
+ if (Array.isArray(parent)) {
299
+ if (typeof key !== "number") {
300
+ throw new Error("Invalid array remove path");
301
+ }
302
+ parent.splice(key, 1);
303
+ return root;
304
+ }
305
+ if (!isRecord(parent) || typeof key !== "string") {
306
+ throw new Error("Invalid object remove path");
307
+ }
308
+ delete parent[key];
309
+ return root;
310
+ }
311
+ /**
312
+ * @param {unknown} root
313
+ * @param {JsonPath} path
314
+ * @returns {{ parent: unknown; key: JsonPathSegment }}
315
+ */
316
+ function getParentAndKey(root, path) {
317
+ let cursor = root;
318
+ for (let i = 0; i < path.length - 1; i += 1) {
319
+ const seg = path[i];
320
+ if (typeof seg === "number") {
321
+ if (!Array.isArray(cursor)) {
322
+ throw new Error("Invalid numeric path segment");
323
+ }
324
+ cursor = cursor[seg];
325
+ continue;
326
+ }
327
+ if (!isRecord(cursor)) {
328
+ throw new Error("Invalid object path segment");
329
+ }
330
+ cursor = cursor[seg];
331
+ }
332
+ return { parent: cursor, key: path[path.length - 1] };
333
+ }
334
+ /**
335
+ * @param {unknown} value
336
+ * @param {string | null} fallback
337
+ * @returns {string | null}
338
+ */
339
+ function inferNodeId(value, fallback) {
340
+ if (!isRecord(value))
341
+ return fallback;
342
+ if (value.kind !== "element")
343
+ return fallback;
344
+ const props = value.props;
345
+ if (!isRecord(props))
346
+ return fallback;
347
+ const id = props.id;
348
+ return typeof id === "string" && id.length > 0 ? id : fallback;
349
+ }
350
+ /**
351
+ * @param {unknown} a
352
+ * @param {unknown} b
353
+ * @returns {boolean}
354
+ */
355
+ function deepEqual(a, b) {
356
+ if (Object.is(a, b))
357
+ return true;
358
+ if (typeof a !== typeof b)
359
+ return false;
360
+ if (a === null || b === null)
361
+ return a === b;
362
+ if (Array.isArray(a) || Array.isArray(b)) {
363
+ if (!Array.isArray(a) || !Array.isArray(b))
364
+ return false;
365
+ if (a.length !== b.length)
366
+ return false;
367
+ for (let i = 0; i < a.length; i += 1) {
368
+ if (!deepEqual(a[i], b[i]))
369
+ return false;
370
+ }
371
+ return true;
372
+ }
373
+ if (isRecord(a) || isRecord(b)) {
374
+ if (!isRecord(a) || !isRecord(b))
375
+ return false;
376
+ const keysA = Object.keys(a).sort();
377
+ const keysB = Object.keys(b).sort();
378
+ if (keysA.length !== keysB.length)
379
+ return false;
380
+ for (let i = 0; i < keysA.length; i += 1) {
381
+ if (keysA[i] !== keysB[i])
382
+ return false;
383
+ const key = keysA[i];
384
+ if (!deepEqual(a[key], b[key]))
385
+ return false;
386
+ }
387
+ return true;
388
+ }
389
+ return false;
390
+ }
391
+ /**
392
+ * @param {unknown} value
393
+ * @returns {value is Record<string, unknown>}
394
+ */
395
+ function isRecord(value) {
396
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
397
+ }
398
+ /**
399
+ * @template T
400
+ * @param {T} value
401
+ * @returns {T}
402
+ */
403
+ function cloneValue(value) {
404
+ if (value === null)
405
+ return value;
406
+ if (typeof value !== "object")
407
+ return value;
408
+ return JSON.parse(JSON.stringify(value));
409
+ }
@@ -0,0 +1,11 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { SqlMessageStorage } from "./SqlMessageStorage.js";
3
+ /** @typedef {import("drizzle-orm/bun-sqlite").BunSQLiteDatabase} BunSQLiteDatabase */
4
+
5
+ /**
6
+ * @param {BunSQLiteDatabase<any> | Database} db
7
+ * @returns {SqlMessageStorage}
8
+ */
9
+ export function getSqlMessageStorage(db) {
10
+ return new SqlMessageStorage(db);
11
+ }