@sixb/sqlite 0.1.0 → 0.1.2

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 (110) hide show
  1. package/README.md +18 -13
  2. package/dist/action-run-storage.d.ts +5 -2
  3. package/dist/action-run-storage.d.ts.map +1 -1
  4. package/dist/agents/agent-storage.d.ts +4 -2
  5. package/dist/agents/agent-storage.d.ts.map +1 -1
  6. package/dist/agents/rows.d.ts +3 -10
  7. package/dist/agents/rows.d.ts.map +1 -1
  8. package/dist/agents/runs.d.ts +3 -2
  9. package/dist/agents/runs.d.ts.map +1 -1
  10. package/dist/ai-usage-storage.d.ts +22 -0
  11. package/dist/ai-usage-storage.d.ts.map +1 -0
  12. package/dist/auth-storage/service-accounts.d.ts.map +1 -1
  13. package/dist/auth-storage/shared.d.ts +1 -10
  14. package/dist/auth-storage/shared.d.ts.map +1 -1
  15. package/dist/connector-connection-storage.d.ts +9 -0
  16. package/dist/connector-connection-storage.d.ts.map +1 -0
  17. package/dist/execution-storage.d.ts +14 -0
  18. package/dist/execution-storage.d.ts.map +1 -0
  19. package/dist/index.d.ts +13 -9
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +4114 -1149
  22. package/dist/index.js.map +30 -27
  23. package/dist/latest-run-query.d.ts.map +1 -1
  24. package/dist/migrations.d.ts +0 -1
  25. package/dist/migrations.d.ts.map +1 -1
  26. package/dist/ontology-storage/materialization-session.d.ts +50 -8
  27. package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
  28. package/dist/ontology-storage/materialization-state.d.ts +4 -5
  29. package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
  30. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
  31. package/dist/ontology-storage/materializations.d.ts +0 -2
  32. package/dist/ontology-storage/materializations.d.ts.map +1 -1
  33. package/dist/ontology-storage/outbox.d.ts.map +1 -1
  34. package/dist/ontology-storage/shared.d.ts +3 -3
  35. package/dist/ontology-storage/shared.d.ts.map +1 -1
  36. package/dist/ontology-storage/sources.d.ts.map +1 -1
  37. package/dist/pipeline-run-storage.d.ts +7 -2
  38. package/dist/pipeline-run-storage.d.ts.map +1 -1
  39. package/dist/projection-run-storage.d.ts +9 -2
  40. package/dist/projection-run-storage.d.ts.map +1 -1
  41. package/dist/run-list-query.d.ts +1 -1
  42. package/dist/run-list-query.d.ts.map +1 -1
  43. package/dist/sync-run-storage.d.ts +7 -2
  44. package/dist/sync-run-storage.d.ts.map +1 -1
  45. package/dist/testing.d.ts +6 -0
  46. package/dist/testing.d.ts.map +1 -0
  47. package/dist/transactions.d.ts +3 -0
  48. package/dist/transactions.d.ts.map +1 -1
  49. package/dist/webhook-run-storage.d.ts +9 -2
  50. package/dist/webhook-run-storage.d.ts.map +1 -1
  51. package/dist/workflow-run-storage.d.ts +7 -3
  52. package/dist/workflow-run-storage.d.ts.map +1 -1
  53. package/package.json +4 -3
  54. package/src/action-run-storage.ts +51 -74
  55. package/src/agents/agent-storage.ts +5 -3
  56. package/src/agents/rows.ts +8 -40
  57. package/src/agents/runs.ts +27 -21
  58. package/src/ai-usage-storage.ts +324 -0
  59. package/src/auth-storage/service-accounts.ts +14 -21
  60. package/src/auth-storage/shared.ts +0 -22
  61. package/src/connector-connection-storage.ts +784 -0
  62. package/src/execution-storage.ts +158 -0
  63. package/src/index.ts +63 -22
  64. package/src/latest-run-query.ts +7 -1
  65. package/src/migrations/002-workflow-run-output.sql +27 -0
  66. package/src/migrations/003-merge-sync-runs.sql +71 -0
  67. package/src/migrations/004-executions.sql +142 -0
  68. package/src/migrations/005-workflow-executions.sql +7 -0
  69. package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
  70. package/src/migrations/007-split-overrides.sql +154 -0
  71. package/src/migrations/008-action-executions.sql +4 -0
  72. package/src/migrations/009-agent-executions.sql +115 -0
  73. package/src/migrations/010-ai-usage-accounting-foundation.sql +74 -0
  74. package/src/migrations/011-sync-failure-record.sql +22 -0
  75. package/src/migrations/012-pipeline-failure-record.sql +45 -0
  76. package/src/migrations/013-workflow-failure-record.sql +45 -0
  77. package/src/migrations/014-agent-failure-record.sql +59 -0
  78. package/src/migrations/015-projection-failure-record.sql +20 -0
  79. package/src/migrations/016-webhook-run-failure-record.sql +19 -0
  80. package/src/migrations/017-action-failure-record.sql +65 -0
  81. package/src/migrations/018-ontology-outbox-failure-record.sql +80 -0
  82. package/src/migrations/019-webhook-delivery-failure-record.sql +62 -0
  83. package/src/migrations/020-drop-run-usage-projections.sql +7 -0
  84. package/src/migrations/021-sync-pipeline-executions.sql +101 -0
  85. package/src/migrations/022-projection-executions.sql +244 -0
  86. package/src/migrations/023-webhook-executions.sql +51 -0
  87. package/src/migrations/024-ontology-commit-executions.sql +58 -0
  88. package/src/migrations/025-connector-connections.sql +257 -0
  89. package/src/migrations.ts +126 -5
  90. package/src/ontology-storage/materialization-session.ts +317 -143
  91. package/src/ontology-storage/materialization-state.ts +398 -150
  92. package/src/ontology-storage/materialization-writer.ts +142 -67
  93. package/src/ontology-storage/materializations.ts +67 -141
  94. package/src/ontology-storage/outbox.ts +10 -3
  95. package/src/ontology-storage/shared.ts +10 -4
  96. package/src/ontology-storage/sources.ts +4 -1
  97. package/src/pipeline-run-storage.ts +128 -43
  98. package/src/projection-run-storage.ts +209 -97
  99. package/src/run-list-query.ts +9 -4
  100. package/src/sync-run-storage.ts +120 -34
  101. package/src/testing.ts +20 -0
  102. package/src/transactions.ts +10 -3
  103. package/src/webhook-run-storage.ts +118 -74
  104. package/src/workflow-run-storage.ts +124 -133
  105. package/dist/validation.d.ts +0 -5
  106. package/dist/validation.d.ts.map +0 -1
  107. package/dist/webhook-delivery-storage.d.ts +0 -27
  108. package/dist/webhook-delivery-storage.d.ts.map +0 -1
  109. package/src/validation.ts +0 -30
  110. package/src/webhook-delivery-storage.ts +0 -272
@@ -1,17 +1,24 @@
1
1
  import type { Database } from "bun:sqlite"
2
2
  import type { JsonValue } from "@sixb/core"
3
+ import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
4
+ import { assertSyncRunExecution } from "@sixb/core/internal/sync-run-storage-provider"
3
5
  import type {
6
+ ExecutionStorage,
4
7
  FinishSyncRunInput,
5
8
  ListLatestSyncRunsInput,
6
9
  ListLatestSyncRunsResult,
7
10
  ListSyncRunsInput,
8
11
  ListSyncRunsResult,
12
+ QueueSyncRunInput,
9
13
  StartSyncRunInput,
10
- SyncRunFailure,
11
14
  SyncRunRecord,
12
15
  SyncRunStorage,
13
16
  } from "@sixb/core/storage"
14
- import { SyncRunError } from "@sixb/core/storage"
17
+ import {
18
+ canRequeueSyncRunAfterEnqueueFailure,
19
+ SYNC_RUN_FAILURE_CODES,
20
+ SyncRunError,
21
+ } from "@sixb/core/storage"
15
22
  import { queryLatestRunsByOwnerId } from "./latest-run-query"
16
23
  import { installFreshSqliteSchema } from "./migrations"
17
24
  import {
@@ -27,6 +34,8 @@ import {
27
34
  } from "./transactions"
28
35
 
29
36
  export interface SqliteSyncRunStorageOptions {
37
+ /** Execution lookup sharing the same provider transaction. */
38
+ executions: ExecutionStorage
30
39
  /** Path to SQLite database file. Defaults to ':memory:' for in-memory database. */
31
40
  path?: string
32
41
  /** Internal shared connection used by bundled SqliteStorage. */
@@ -36,18 +45,27 @@ export interface SqliteSyncRunStorageOptions {
36
45
  export class SqliteSyncRunStorage implements SyncRunStorage {
37
46
  private readonly connection: SqliteStoreConnection
38
47
  private readonly db: Database
48
+ private readonly executions: ExecutionStorage
39
49
 
40
- constructor(options: SqliteSyncRunStorageOptions = {}) {
50
+ constructor(options: SqliteSyncRunStorageOptions) {
41
51
  this.connection = openSqliteStoreConnection(options)
42
52
  this.db = this.connection.db
53
+ this.executions = options.executions
43
54
 
44
55
  if (this.connection.installFreshSchema) {
45
56
  installFreshSqliteSchema(this.db)
46
57
  }
47
58
  }
48
59
 
49
- async start(input: StartSyncRunInput): Promise<SyncRunRecord> {
50
- const startedAt = input.startedAt ?? new Date()
60
+ async queue(input: QueueSyncRunInput): Promise<SyncRunRecord> {
61
+ const queuedAt = input.queuedAt ?? new Date()
62
+ await assertSyncRunExecution({
63
+ executions: this.executions,
64
+ projectId: input.projectId,
65
+ executionId: input.executionId,
66
+ runId: input.id,
67
+ syncId: input.syncId,
68
+ })
51
69
 
52
70
  try {
53
71
  this.db
@@ -56,32 +74,33 @@ export class SqliteSyncRunStorage implements SyncRunStorage {
56
74
  INSERT INTO sync_runs (
57
75
  project_id,
58
76
  id,
77
+ execution_id,
59
78
  sync_id,
60
79
  dataset_id,
61
80
  mode,
62
81
  status,
82
+ queued_at,
63
83
  started_at,
64
84
  expected_latest_version_id,
65
85
  commit_message
66
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
86
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)
67
87
  `
68
88
  )
69
89
  .run(
70
90
  input.projectId,
71
91
  input.id,
92
+ input.executionId,
72
93
  input.syncId,
73
94
  input.datasetId,
74
95
  input.mode,
75
- "running",
76
- startedAt.toISOString(),
96
+ "queued",
97
+ queuedAt.toISOString(),
77
98
  input.expectedLatestVersionId ?? null,
78
99
  input.commitMessage ?? null
79
100
  )
80
101
  } catch (error) {
81
102
  if (isUniqueConstraintError(error)) {
82
- throw new SyncRunError(
83
- `[SixbSqlite] Sync run '${input.id}' already exists for project '${input.projectId}'.`
84
- )
103
+ return this.requeueAfterEnqueueFailure(input, queuedAt)
85
104
  }
86
105
 
87
106
  throw error
@@ -97,6 +116,70 @@ export class SqliteSyncRunStorage implements SyncRunStorage {
97
116
  return record
98
117
  }
99
118
 
119
+ private async requeueAfterEnqueueFailure(
120
+ input: QueueSyncRunInput,
121
+ queuedAt: Date
122
+ ): Promise<SyncRunRecord> {
123
+ return this.db.transaction(() => {
124
+ const existing = this.db
125
+ .query("SELECT * FROM sync_runs WHERE project_id = ? AND id = ?")
126
+ .get(input.projectId, input.id) as DatabaseRow | null
127
+ if (!existing || !canRequeueSyncRunAfterEnqueueFailure(rowToSyncRunRecord(existing), input)) {
128
+ throw new SyncRunError(
129
+ `[SixbSqlite] Sync run '${input.id}' already exists for project '${input.projectId}'.`
130
+ )
131
+ }
132
+
133
+ this.db
134
+ .query(
135
+ `
136
+ UPDATE sync_runs
137
+ SET status = ?, queued_at = ?, started_at = NULL, finished_at = NULL,
138
+ rows_read = NULL, output_version_id = NULL, error = NULL, checkpoint = NULL
139
+ WHERE project_id = ? AND id = ?
140
+ `
141
+ )
142
+ .run("queued", queuedAt.toISOString(), input.projectId, input.id)
143
+
144
+ const updated = this.db
145
+ .query("SELECT * FROM sync_runs WHERE project_id = ? AND id = ?")
146
+ .get(input.projectId, input.id) as DatabaseRow
147
+ return rowToSyncRunRecord(updated)
148
+ })()
149
+ }
150
+
151
+ async start(input: StartSyncRunInput): Promise<SyncRunRecord> {
152
+ const result = this.db
153
+ .query(
154
+ `
155
+ UPDATE sync_runs
156
+ SET status = ?, started_at = ?, error = NULL
157
+ WHERE project_id = ? AND id = ? AND status = ?
158
+ `
159
+ )
160
+ .run(
161
+ "running",
162
+ (input.startedAt ?? new Date()).toISOString(),
163
+ input.projectId,
164
+ input.id,
165
+ "queued"
166
+ )
167
+ if (result.changes > 0) {
168
+ const record = await this.getById({ projectId: input.projectId, id: input.id })
169
+ if (record) return record
170
+ }
171
+
172
+ const existing = await this.getById({ projectId: input.projectId, id: input.id })
173
+ if (!existing) {
174
+ throw new SyncRunError(
175
+ `[SixbSqlite] Sync run '${input.id}' not found for project '${input.projectId}'.`
176
+ )
177
+ }
178
+ throw new SyncRunError(
179
+ `[SixbSqlite] Sync run '${input.id}' cannot start from status '${existing.status}'.`
180
+ )
181
+ }
182
+
100
183
  async finish(input: FinishSyncRunInput): Promise<SyncRunRecord> {
101
184
  return this.db.transaction(() => {
102
185
  const existing = this.db
@@ -109,15 +192,26 @@ export class SqliteSyncRunStorage implements SyncRunStorage {
109
192
  )
110
193
  }
111
194
 
195
+ const enqueueFailure =
196
+ input.status === "failed" && input.error.code === "queue.enqueue_failed"
197
+ if (
198
+ (enqueueFailure && existing.status !== "queued") ||
199
+ (!enqueueFailure && existing.status !== "running")
200
+ ) {
201
+ throw new SyncRunError(
202
+ `[SixbSqlite] Sync run '${input.id}' cannot finish from status '${existing.status}'.`
203
+ )
204
+ }
205
+
112
206
  if (input.status === "succeeded") {
113
207
  if (input.output && input.output.datasetId !== existing.dataset_id) {
114
208
  throw new SyncRunError(
115
209
  `[SixbSqlite] Sync run '${input.id}' output dataset '${input.output.datasetId}' does not match '${existing.dataset_id}'.`
116
210
  )
117
211
  }
118
- if (!input.output && (existing.mode !== "append" || input.rowsRead !== 0)) {
212
+ if (!input.output && input.rowsRead !== 0 && existing.mode !== "merge") {
119
213
  throw new SyncRunError(
120
- `[SixbSqlite] Sync run '${input.id}' may omit its output only for an empty append.`
214
+ `[SixbSqlite] Sync run '${input.id}' may omit its output with rows read only for an initial merge no-op.`
121
215
  )
122
216
  }
123
217
  }
@@ -131,8 +225,7 @@ export class SqliteSyncRunStorage implements SyncRunStorage {
131
225
  finished_at = ?,
132
226
  rows_read = ?,
133
227
  output_version_id = ?,
134
- error_name = ?,
135
- error_message = ?,
228
+ error = ?,
136
229
  checkpoint = ?
137
230
  WHERE project_id = ? AND id = ?
138
231
  `
@@ -144,8 +237,9 @@ export class SqliteSyncRunStorage implements SyncRunStorage {
144
237
  ? input.rowsRead
145
238
  : (input.rowsRead ?? existing.rows_read ?? null),
146
239
  input.status === "succeeded" ? (input.output?.versionId ?? null) : null,
147
- input.status === "succeeded" ? null : (input.error?.name ?? null),
148
- input.status === "succeeded" ? null : (input.error?.message ?? null),
240
+ input.status === "succeeded" || input.error === undefined
241
+ ? null
242
+ : serializeSixbFailure(input.error, SYNC_RUN_FAILURE_CODES),
149
243
  input.status === "succeeded" ? serializeCheckpoint(input.checkpoint) : null,
150
244
  input.projectId,
151
245
  input.id
@@ -194,7 +288,7 @@ export class SqliteSyncRunStorage implements SyncRunStorage {
194
288
  args.push(input.datasetId)
195
289
  }
196
290
 
197
- appendRunListFilters(whereClauses, args, input)
291
+ appendRunListFilters(whereClauses, args, input, "COALESCE(started_at, queued_at)")
198
292
 
199
293
  const { rows, total, hasMore } = queryRunList<DatabaseRow>({
200
294
  db: this.db,
@@ -241,26 +335,17 @@ function parseCheckpoint(value: string | null | undefined): JsonValue | undefine
241
335
  return value != null ? (JSON.parse(value) as JsonValue) : undefined
242
336
  }
243
337
 
244
- function toSyncRunFailure(row: DatabaseRow): SyncRunFailure | undefined {
245
- if (!row.error_message) {
246
- return undefined
247
- }
248
-
249
- return {
250
- name: row.error_name ?? undefined,
251
- message: row.error_message,
252
- }
253
- }
254
-
255
338
  function rowToSyncRunRecord(row: DatabaseRow): SyncRunRecord {
256
339
  return {
257
340
  id: row.id,
258
341
  projectId: row.project_id,
342
+ executionId: row.execution_id,
259
343
  syncId: row.sync_id,
260
344
  datasetId: row.dataset_id,
261
345
  mode: row.mode,
262
346
  status: row.status,
263
- startedAt: new Date(row.started_at),
347
+ queuedAt: new Date(row.queued_at),
348
+ startedAt: row.started_at ? new Date(row.started_at) : undefined,
264
349
  finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
265
350
  rowsRead: row.rows_read ?? undefined,
266
351
  output: row.output_version_id
@@ -271,7 +356,7 @@ function rowToSyncRunRecord(row: DatabaseRow): SyncRunRecord {
271
356
  : undefined,
272
357
  expectedLatestVersionId: row.expected_latest_version_id ?? undefined,
273
358
  commitMessage: row.commit_message ?? undefined,
274
- error: toSyncRunFailure(row),
359
+ error: row.error === null ? undefined : parseSixbFailure(row.error, SYNC_RUN_FAILURE_CODES),
275
360
  checkpoint: parseCheckpoint(row.checkpoint),
276
361
  }
277
362
  }
@@ -283,17 +368,18 @@ function isUniqueConstraintError(error: unknown): boolean {
283
368
  interface DatabaseRow {
284
369
  project_id: string
285
370
  id: string
371
+ execution_id: string
286
372
  sync_id: string
287
373
  dataset_id: string
288
374
  mode: SyncRunRecord["mode"]
289
375
  status: SyncRunRecord["status"]
290
- started_at: string
376
+ queued_at: string
377
+ started_at: string | null
291
378
  finished_at: string | null
292
379
  rows_read: number | null
293
380
  output_version_id: string | null
294
381
  expected_latest_version_id: string | null
295
382
  commit_message: string | null
296
- error_name: string | null
297
- error_message: string | null
383
+ error: string | null
298
384
  checkpoint: string | null
299
385
  }
package/src/testing.ts ADDED
@@ -0,0 +1,20 @@
1
+ export interface SqliteStorageTestingAdapter {
2
+ advanceConnectorConnectionTime(durationMs: number): void
3
+ }
4
+
5
+ const adapters = new WeakMap<object, SqliteStorageTestingAdapter>()
6
+
7
+ export function registerSqliteStorageTestingAdapter(
8
+ storage: object,
9
+ advanceConnectorConnectionTime: (durationMs: number) => void
10
+ ): void {
11
+ adapters.set(storage, {
12
+ advanceConnectorConnectionTime,
13
+ })
14
+ }
15
+
16
+ export function getSqliteStorageTestingAdapter(storage: object): SqliteStorageTestingAdapter {
17
+ const adapter = adapters.get(storage)
18
+ if (!adapter) throw new Error("[SixbSqlite] Storage testing adapter is unavailable.")
19
+ return adapter
20
+ }
@@ -12,6 +12,7 @@ export interface SqliteStoreConnection {
12
12
  export interface SqliteStoreConnectionOptions {
13
13
  readonly path?: string
14
14
  readonly connection?: SqliteStoreConnection
15
+ readonly readonly?: boolean
15
16
  }
16
17
 
17
18
  export function openSqliteStoreConnection(
@@ -26,13 +27,14 @@ export function openSqliteStoreConnection(
26
27
  }
27
28
 
28
29
  const path = options.path ?? ":memory:"
29
- if (path !== ":memory:") mkdirSync(dirname(path), { recursive: true })
30
- const db = new SqliteDatabase(path)
30
+ if (path !== ":memory:" && !options.readonly) mkdirSync(dirname(path), { recursive: true })
31
+ const db = new SqliteDatabase(path, options.readonly ? { readonly: true } : undefined)
31
32
  db.run("PRAGMA foreign_keys = ON")
33
+ db.run("PRAGMA busy_timeout = 5000")
32
34
  return {
33
35
  db,
34
36
  ownsConnection: true,
35
- installFreshSchema: path === ":memory:",
37
+ installFreshSchema: path === ":memory:" && !options.readonly,
36
38
  }
37
39
  }
38
40
 
@@ -64,6 +66,11 @@ export function runImmediateTransaction<T>(db: Database, run: () => T): T {
64
66
  }
65
67
  }
66
68
 
69
+ /** Lets timers and request handlers run between bounded synchronous SQLite chunks. */
70
+ export function yieldSqliteEventLoop(): Promise<void> {
71
+ return new Promise((resolve) => setImmediate(resolve))
72
+ }
73
+
67
74
  export async function runImmediateTransactionAsync<T>(
68
75
  db: Database,
69
76
  run: () => Promise<T> | T