orez 0.2.26 → 0.2.29

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 (172) hide show
  1. package/dist/cf-do/worker.d.ts.map +1 -1
  2. package/dist/cf-do/worker.js +9 -1
  3. package/dist/cf-do/worker.js.map +1 -1
  4. package/dist/pg-proxy-do-backend.d.ts +2 -0
  5. package/dist/pg-proxy-do-backend.d.ts.map +1 -1
  6. package/dist/pg-proxy-do-backend.js +49 -7
  7. package/dist/pg-proxy-do-backend.js.map +1 -1
  8. package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
  9. package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
  10. package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
  11. package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
  12. package/dist/pg-sqlite-compiler/index.d.ts +12 -0
  13. package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
  14. package/dist/pg-sqlite-compiler/index.js +59 -0
  15. package/dist/pg-sqlite-compiler/index.js.map +1 -0
  16. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
  17. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
  18. package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
  19. package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
  20. package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
  21. package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
  22. package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
  23. package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
  24. package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
  25. package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
  26. package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
  27. package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
  28. package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
  29. package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
  30. package/dist/pg-sqlite-compiler/passes/index.js +39 -0
  31. package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
  32. package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
  33. package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
  34. package/dist/pg-sqlite-compiler/passes/types.js +103 -0
  35. package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
  36. package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
  37. package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
  38. package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
  39. package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
  40. package/dist/pg-sqlite-compiler/types.d.ts +55 -0
  41. package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
  42. package/dist/pg-sqlite-compiler/types.js +2 -0
  43. package/dist/pg-sqlite-compiler/types.js.map +1 -0
  44. package/package.json +8 -4
  45. package/src/admin/admin-data.test.ts +0 -348
  46. package/src/admin/http-proxy.ts +0 -252
  47. package/src/admin/log-store.ts +0 -192
  48. package/src/admin/server.ts +0 -471
  49. package/src/admin/ui.ts +0 -1322
  50. package/src/bench/proxy-throughput.bench.ts +0 -343
  51. package/src/bench/serial-mutations.bench.ts +0 -270
  52. package/src/browser.ts +0 -203
  53. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  54. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  55. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  56. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  57. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
  58. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
  59. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
  60. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
  61. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
  62. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
  63. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
  64. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
  65. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
  66. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
  67. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
  68. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
  69. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
  70. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
  71. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
  72. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  73. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  74. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  75. package/src/cf-do/ARCHITECTURE.md +0 -83
  76. package/src/cf-do/watermark.test.ts +0 -103
  77. package/src/cf-do/watermark.ts +0 -118
  78. package/src/cf-do/worker.ts +0 -1033
  79. package/src/cf-do/wrangler.toml +0 -11
  80. package/src/cf-pglite/README.md +0 -19
  81. package/src/change-tracking.ts +0 -25
  82. package/src/child-process.test.ts +0 -147
  83. package/src/child-process.ts +0 -90
  84. package/src/cli-entry.ts +0 -72
  85. package/src/cli.test.ts +0 -38
  86. package/src/cli.ts +0 -1214
  87. package/src/config.ts +0 -150
  88. package/src/do-sql-tracking.test.ts +0 -19
  89. package/src/do-sql-tracking.ts +0 -19
  90. package/src/index.ts +0 -1215
  91. package/src/integration/integration.test.ts +0 -517
  92. package/src/integration/native-binary.guard.test.ts +0 -13
  93. package/src/integration/native-startup.test.ts +0 -44
  94. package/src/integration/replication-latency.test.ts +0 -428
  95. package/src/integration/restore-live-stress.test.ts +0 -433
  96. package/src/integration/restore-reset.test.ts +0 -400
  97. package/src/integration/restore.test.ts +0 -274
  98. package/src/integration/test-permissions.ts +0 -147
  99. package/src/load-config.ts +0 -46
  100. package/src/log.ts +0 -96
  101. package/src/mutex.ts +0 -47
  102. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  103. package/src/pg-proxy-browser.ts +0 -2022
  104. package/src/pg-proxy-do-backend.test.ts +0 -3890
  105. package/src/pg-proxy-do-backend.ts +0 -7157
  106. package/src/pg-proxy.ts +0 -1087
  107. package/src/pglite-ipc.test.ts +0 -116
  108. package/src/pglite-ipc.ts +0 -266
  109. package/src/pglite-manager.ts +0 -557
  110. package/src/pglite-web-proxy.test.ts +0 -57
  111. package/src/pglite-web-proxy.ts +0 -221
  112. package/src/pglite-web-worker.ts +0 -152
  113. package/src/pglite-worker-thread.ts +0 -253
  114. package/src/port.ts +0 -25
  115. package/src/process-title.ts +0 -9
  116. package/src/recovery.ts +0 -155
  117. package/src/replication/change-tracker.test.ts +0 -357
  118. package/src/replication/change-tracker.ts +0 -279
  119. package/src/replication/handler.test.ts +0 -511
  120. package/src/replication/handler.ts +0 -1190
  121. package/src/replication/pgoutput-encoder.test.ts +0 -697
  122. package/src/replication/pgoutput-encoder.ts +0 -373
  123. package/src/replication/tcp-replication.test.ts +0 -876
  124. package/src/replication/zero-compat.test.ts +0 -1150
  125. package/src/restore-stress.test.ts +0 -188
  126. package/src/s3-local.ts +0 -203
  127. package/src/shim/hooks.mjs +0 -120
  128. package/src/shim/register.mjs +0 -4
  129. package/src/sqlite-mode/apply-mode.ts +0 -224
  130. package/src/sqlite-mode/index.ts +0 -15
  131. package/src/sqlite-mode/native-binary.ts +0 -89
  132. package/src/sqlite-mode/package-resolve.ts +0 -17
  133. package/src/sqlite-mode/resolve-mode.ts +0 -80
  134. package/src/sqlite-mode/shim-template.ts +0 -159
  135. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  136. package/src/sqlite-mode/types.ts +0 -30
  137. package/src/vite-plugin.ts +0 -67
  138. package/src/wasm-sqlite.test.ts +0 -537
  139. package/src/worker/browser-admin.ts +0 -52
  140. package/src/worker/browser-build-config.test.ts +0 -71
  141. package/src/worker/browser-build-config.ts +0 -109
  142. package/src/worker/browser-embed-admin.test.ts +0 -75
  143. package/src/worker/browser-embed.ts +0 -345
  144. package/src/worker/cf-patches.ts +0 -384
  145. package/src/worker/embed-integration.test.ts +0 -321
  146. package/src/worker/index.ts +0 -138
  147. package/src/worker/shims/fastify.test.ts +0 -255
  148. package/src/worker/shims/fastify.ts +0 -306
  149. package/src/worker/shims/http-service.test.ts +0 -355
  150. package/src/worker/shims/http-service.ts +0 -293
  151. package/src/worker/shims/node-stub.ts +0 -290
  152. package/src/worker/shims/oxfmt.ts +0 -3
  153. package/src/worker/shims/postgres-browser.ts +0 -59
  154. package/src/worker/shims/postgres-socket.test.ts +0 -576
  155. package/src/worker/shims/postgres-socket.ts +0 -310
  156. package/src/worker/shims/postgres.test.ts +0 -364
  157. package/src/worker/shims/postgres.ts +0 -1454
  158. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  159. package/src/worker/shims/sqlite-browser.ts +0 -175
  160. package/src/worker/shims/sqlite.test.ts +0 -786
  161. package/src/worker/shims/sqlite.ts +0 -978
  162. package/src/worker/shims/stream-browser.ts +0 -15
  163. package/src/worker/shims/ws-browser.test.ts +0 -205
  164. package/src/worker/shims/ws-browser.ts +0 -248
  165. package/src/worker/shims/ws.test.ts +0 -288
  166. package/src/worker/shims/ws.ts +0 -467
  167. package/src/worker/shims/zero-process-env.ts +0 -11
  168. package/src/worker/types.ts +0 -75
  169. package/src/worker/worker-integration.test.ts +0 -223
  170. package/src/worker/worker.test.ts +0 -136
  171. package/src/worker/zero-cache-embed-cf.ts +0 -463
  172. package/src/worker/zero-cache-embed.ts +0 -277
@@ -1,978 +0,0 @@
1
- // NOTE THIS IS NOT OREZ NODE THIS IS NOT A GOOD REFERENCE BECAUSE ITS OUR EARLY GUESS AT WHAT COULD WORK
2
- // DO NOT STUDY THIS, THE OTHER STUFF IN SRC IS WHERE YOU EANT TO LOOK
3
-
4
- /**
5
- * sqlite shim for cloudflare durable objects.
6
- *
7
- * wraps the DO SqlStorage interface (`this.ctx.storage.sql`) to implement
8
- * the better-sqlite3 / @rocicorp/zero-sqlite3 api that zero-cache uses.
9
- *
10
- * all operations are synchronous, matching both DO sqlite and better-sqlite3.
11
- *
12
- * usage in a durable object:
13
- *
14
- * import { Database } from 'orez/worker/shims/sqlite'
15
- *
16
- * export class MyDO extends DurableObject {
17
- * db: Database
18
- * constructor(ctx: DurableObjectState, env: Env) {
19
- * super(ctx, env)
20
- * this.db = new Database(ctx.storage.sql)
21
- * }
22
- * }
23
- */
24
-
25
- // -- abstract interface for DO SqlStorage --
26
-
27
- export type SqlStorageValue = string | number | null | ArrayBuffer
28
-
29
- export interface SqlStorageCursor {
30
- toArray(): Record<string, SqlStorageValue>[]
31
- readonly rowsRead: number
32
- readonly rowsWritten: number
33
- readonly columnNames: string[]
34
- }
35
-
36
- export interface SqlStorageLike {
37
- exec(query: string, ...bindings: SqlStorageValue[]): SqlStorageCursor
38
- /** DO transaction API — if available, used instead of raw BEGIN/COMMIT */
39
- transactionSync?<T>(fn: () => T): T
40
- }
41
-
42
- type SqliteConnectionRole = 'default' | 'replica-writer'
43
- const activeSnapshotPrefixes = new Set<string>()
44
-
45
- // -- SqliteError --
46
-
47
- export class SqliteError extends Error {
48
- code: string
49
-
50
- constructor(message: string, code: string) {
51
- super(message)
52
- this.name = 'SqliteError'
53
- this.code = code
54
- }
55
- }
56
-
57
- // -- RunResult --
58
-
59
- export interface RunResult {
60
- changes: number
61
- lastInsertRowid: number | bigint
62
- }
63
-
64
- // -- parameter serialization --
65
- // sqlite only accepts scalar values; serialize objects/booleans/dates/etc.
66
- // special case: a single object argument means named parameters (@key syntax)
67
- // convert named params (@key) in SQL to positional (?) and extract values in order.
68
- // CF DO SqlStorage doesn't support @key syntax, only ? placeholders.
69
- function convertNamedParams(
70
- sql: string,
71
- params: Record<string, unknown>
72
- ): { sql: string; values: SqlStorageValue[] } {
73
- const values: SqlStorageValue[] = []
74
- // each @param occurrence gets its own ? placeholder and value
75
- const converted = sql.replace(/@(\w+)/g, (_, name) => {
76
- values.push(serializeValue(params[name]))
77
- return '?'
78
- })
79
- return { sql: converted, values }
80
- }
81
-
82
- function serializeValue(p: unknown): SqlStorageValue {
83
- if (p === null || p === undefined) return null
84
- if (typeof p === 'string' || typeof p === 'number') return p
85
- if (typeof p === 'boolean') return p ? 1 : 0
86
- if (typeof p === 'bigint') return Number(p)
87
- if (p instanceof ArrayBuffer || p instanceof Uint8Array) return p as any
88
- if (typeof p === 'object') return JSON.stringify(p)
89
- return String(p)
90
- }
91
-
92
- function serializeSqliteParams(params: unknown[]): SqlStorageValue[] {
93
- // better-sqlite3 API: stmt.run([val1, val2, ...]) spreads the array
94
- // as positional parameters. detect this: single array argument.
95
- if (params.length === 1 && Array.isArray(params[0])) {
96
- return serializeSqliteParams(params[0])
97
- }
98
-
99
- // named parameters: .run({key: value}) → extract values matching @key placeholders
100
- // handled at the exec level via convertNamedParams, return as marker here
101
- if (
102
- params.length === 1 &&
103
- params[0] !== null &&
104
- typeof params[0] === 'object' &&
105
- !Array.isArray(params[0]) &&
106
- !(params[0] instanceof ArrayBuffer) &&
107
- !(params[0] instanceof Uint8Array)
108
- ) {
109
- return params as any // marker: the Statement methods detect this and convert
110
- }
111
-
112
- return params.map(serializeValue)
113
- }
114
-
115
- function quoteIdentifier(value: string): string {
116
- return `"${value.replace(/"/g, '""')}"`
117
- }
118
-
119
- function snapshotTableName(prefix: string, table: string): string {
120
- return `${prefix}_${table.replace(/[^A-Za-z0-9_]/g, '_')}`
121
- }
122
-
123
- function isSnapshotInternalTable(name: string): boolean {
124
- return name.startsWith('_orez_snapshot_')
125
- }
126
-
127
- function createSnapshotPrefix(): string {
128
- const uuid =
129
- typeof globalThis.crypto?.randomUUID === 'function'
130
- ? globalThis.crypto.randomUUID().replace(/-/g, '').slice(0, 16)
131
- : Math.random().toString(36).slice(2, 18)
132
- return `_orez_snapshot_${Date.now().toString(36)}_${uuid}`
133
- }
134
-
135
- function isActiveSnapshotTable(name: string): boolean {
136
- for (const prefix of activeSnapshotPrefixes) {
137
- if (name.startsWith(`${prefix}_`)) return true
138
- }
139
- return false
140
- }
141
-
142
- function cleanupInactiveSnapshotTables(sql: SqlStorageLike): void {
143
- try {
144
- const rows = sql
145
- .exec(
146
- `SELECT name FROM sqlite_master
147
- WHERE type = 'table'
148
- AND name LIKE '_orez_snapshot_%'`
149
- )
150
- .toArray()
151
- for (const row of rows) {
152
- const name = String(row.name ?? '')
153
- if (!isSnapshotInternalTable(name) || isActiveSnapshotTable(name)) continue
154
- try {
155
- sql.exec(`DROP TABLE IF EXISTS ${quoteIdentifier(name)}`)
156
- } catch {}
157
- }
158
- } catch {}
159
- }
160
-
161
- function shouldSnapshotTable(name: string): boolean {
162
- return (
163
- name !== '__miniflare_do_name' &&
164
- name !== 'storage' &&
165
- name !== 'sqlite_stat1' &&
166
- !isSnapshotInternalTable(name)
167
- )
168
- }
169
-
170
- function currentConnectionRole(): SqliteConnectionRole {
171
- return (globalThis as any).__orez_zero_sqlite_role === 'replica-writer'
172
- ? 'replica-writer'
173
- : 'default'
174
- }
175
-
176
- function isSqliteCatalogQuery(sql: string): boolean {
177
- return /\bsqlite_(?:master|schema)\b/i.test(sql)
178
- }
179
-
180
- function hasSnapshotCatalogName(row: Record<string, unknown>): boolean {
181
- for (const key of ['name', 'tbl_name', 'table', 'tableName']) {
182
- const value = row[key]
183
- if (typeof value === 'string' && isSnapshotInternalTable(value)) return true
184
- }
185
- return false
186
- }
187
-
188
- function filterSnapshotCatalogRows<T>(sql: string, rows: T[]): T[] {
189
- if (!isSqliteCatalogQuery(sql)) return rows
190
- return rows.filter((row) => !hasSnapshotCatalogName(row as Record<string, unknown>))
191
- }
192
-
193
- function replaceIdentifierOutsideLiterals(
194
- sql: string,
195
- identifier: string,
196
- replacement: string
197
- ): string {
198
- let out = ''
199
- let i = 0
200
-
201
- while (i < sql.length) {
202
- const ch = sql[i]
203
- const next = sql[i + 1]
204
-
205
- if (ch === "'") {
206
- const start = i
207
- i++
208
- while (i < sql.length) {
209
- if (sql[i] === "'" && sql[i + 1] === "'") {
210
- i += 2
211
- continue
212
- }
213
- if (sql[i] === "'") {
214
- i++
215
- break
216
- }
217
- i++
218
- }
219
- out += sql.slice(start, i)
220
- continue
221
- }
222
-
223
- if (ch === '"') {
224
- const start = i
225
- let value = ''
226
- i++
227
- while (i < sql.length) {
228
- if (sql[i] === '"' && sql[i + 1] === '"') {
229
- value += '"'
230
- i += 2
231
- continue
232
- }
233
- if (sql[i] === '"') {
234
- i++
235
- break
236
- }
237
- value += sql[i]
238
- i++
239
- }
240
- out += value === identifier ? quoteIdentifier(replacement) : sql.slice(start, i)
241
- continue
242
- }
243
-
244
- if (ch === '-' && next === '-') {
245
- const start = i
246
- i += 2
247
- while (i < sql.length && sql[i] !== '\n') i++
248
- out += sql.slice(start, i)
249
- continue
250
- }
251
-
252
- if (ch === '/' && next === '*') {
253
- const start = i
254
- i += 2
255
- while (i < sql.length && !(sql[i] === '*' && sql[i + 1] === '/')) i++
256
- i = Math.min(sql.length, i + 2)
257
- out += sql.slice(start, i)
258
- continue
259
- }
260
-
261
- if (/[A-Za-z_]/.test(ch)) {
262
- const start = i
263
- i++
264
- while (i < sql.length && /[A-Za-z0-9_]/.test(sql[i])) i++
265
- const word = sql.slice(start, i)
266
- out += word === identifier ? replacement : word
267
- continue
268
- }
269
-
270
- out += ch
271
- i++
272
- }
273
-
274
- return out
275
- }
276
-
277
- function rewriteSQLTables(sql: string, tables: Map<string, string>): string {
278
- let rewritten = sql
279
- const names = [...tables.keys()].sort((a, b) => b.length - a.length)
280
- for (const name of names) {
281
- const snapshot = tables.get(name)!
282
- rewritten = replaceIdentifierOutsideLiterals(rewritten, name, snapshot)
283
- }
284
- return rewritten
285
- }
286
-
287
- // -- Statement --
288
-
289
- export class Statement<T = Record<string, SqlStorageValue>> {
290
- readonly source: string
291
- #sql: SqlStorageLike
292
- #db: Database
293
-
294
- constructor(sql: SqlStorageLike, db: Database, source: string) {
295
- this.#sql = sql
296
- this.#db = db
297
- // auto-add IF NOT EXISTS to CREATE TABLE/INDEX (shared sqlite in browser)
298
- this.source = source
299
- .replace(/CREATE\s+TABLE\s+(?!IF\s+NOT\s+EXISTS)/gi, 'CREATE TABLE IF NOT EXISTS ')
300
- .replace(/CREATE\s+INDEX\s+(?!IF\s+NOT\s+EXISTS)/gi, 'CREATE INDEX IF NOT EXISTS ')
301
- .replace(
302
- /CREATE\s+UNIQUE\s+INDEX\s+(?!IF\s+NOT\s+EXISTS)/gi,
303
- 'CREATE UNIQUE INDEX IF NOT EXISTS '
304
- )
305
- }
306
-
307
- // intercept PRAGMAs that sql.js can't handle (wal2, etc.)
308
- #interceptPragma(): { intercepted: boolean; result?: SqlStorageCursor } {
309
- const upper = this.source.trimStart().toUpperCase()
310
- if (!upper.startsWith('PRAGMA')) return { intercepted: false }
311
-
312
- // PRAGMA journal_mode — always report wal2 (sql.js doesn't support WAL)
313
- if (/PRAGMA\s+journal_mode\s*=/i.test(this.source)) {
314
- const value = this.source.split('=')[1]?.trim().replace(/['"]/g, '') || 'wal2'
315
- return {
316
- intercepted: true,
317
- result: {
318
- toArray: () => [{ journal_mode: value }],
319
- rowsRead: 1,
320
- rowsWritten: 0,
321
- columnNames: ['journal_mode'],
322
- },
323
- }
324
- }
325
- if (/PRAGMA\s+journal_mode\s*$/i.test(this.source)) {
326
- return {
327
- intercepted: true,
328
- result: {
329
- toArray: () => [{ journal_mode: 'wal2' }],
330
- rowsRead: 1,
331
- rowsWritten: 0,
332
- columnNames: ['journal_mode'],
333
- },
334
- }
335
- }
336
- return { intercepted: false }
337
- }
338
-
339
- // resolve named params (@key) → positional (?), or return sql + values as-is
340
- #resolveParams(params: unknown[]): { sql: string; values: SqlStorageValue[] } {
341
- const serialized = serializeSqliteParams(params)
342
- // detect named parameter marker: single non-array object
343
- const first = serialized[0] as any
344
- if (
345
- serialized.length === 1 &&
346
- first !== null &&
347
- typeof first === 'object' &&
348
- !Array.isArray(first) &&
349
- !(first instanceof ArrayBuffer) &&
350
- !(first instanceof Uint8Array)
351
- ) {
352
- return convertNamedParams(this.source, first as Record<string, unknown>)
353
- }
354
- return { sql: this.source, values: serialized }
355
- }
356
-
357
- run(...params: unknown[]): RunResult {
358
- if (!this.#db.open) {
359
- throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE')
360
- }
361
- const pragma = this.#interceptPragma()
362
- if (pragma.intercepted) return { changes: 0, lastInsertRowid: 0 }
363
-
364
- const upper = this.source.trimStart().toUpperCase()
365
- const isTxCmd =
366
- upper.startsWith('BEGIN') ||
367
- upper.startsWith('COMMIT') ||
368
- upper.startsWith('ROLLBACK') ||
369
- upper === 'END' ||
370
- upper.startsWith('END ')
371
- const resolved = this.#resolveParams(params)
372
- const sql = this.#db._rewriteForSnapshot(resolved.sql)
373
- const values = resolved.values
374
- const cursor =
375
- isTxCmd && values.length === 0
376
- ? this.#db._execTransactionAware(sql, this.#sql)
377
- : this.#sql.exec(sql, ...values)
378
- return {
379
- changes: cursor.rowsWritten,
380
- lastInsertRowid: 0,
381
- }
382
- }
383
-
384
- get(...params: unknown[]): T | undefined {
385
- if (!this.#db.open) {
386
- throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE')
387
- }
388
- const pragma = this.#interceptPragma()
389
- if (pragma.intercepted && pragma.result) {
390
- return pragma.result.toArray()[0] as T
391
- }
392
-
393
- const resolved = this.#resolveParams(params)
394
- const sql = this.#db._rewriteForSnapshot(resolved.sql)
395
- const values = resolved.values
396
- const cursor = this.#sql.exec(sql, ...values)
397
- const rows = filterSnapshotCatalogRows(sql, cursor.toArray())
398
- return (rows[0] as T) ?? undefined
399
- }
400
-
401
- all(...params: unknown[]): T[] {
402
- if (!this.#db.open) {
403
- throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE')
404
- }
405
- const pragma = this.#interceptPragma()
406
- if (pragma.intercepted && pragma.result) {
407
- return pragma.result.toArray() as T[]
408
- }
409
-
410
- const resolved = this.#resolveParams(params)
411
- const sql = this.#db._rewriteForSnapshot(resolved.sql)
412
- const values = resolved.values
413
- const cursor = this.#sql.exec(sql, ...values)
414
- return filterSnapshotCatalogRows(sql, cursor.toArray()) as T[]
415
- }
416
-
417
- iterate(...params: unknown[]): IterableIterator<T> {
418
- // eagerly fetch all rows - DO sqlite doesn't support streaming
419
- const rows = this.all(...params)
420
- let index = 0
421
- return {
422
- next(): IteratorResult<T> {
423
- if (index < rows.length) {
424
- return { value: rows[index++], done: false }
425
- }
426
- return { value: undefined as unknown as T, done: true }
427
- },
428
- [Symbol.iterator]() {
429
- return this
430
- },
431
- }
432
- }
433
-
434
- /** no-op for compatibility — DO sqlite doesn't need bigint toggle */
435
- safeIntegers(_toggle?: boolean): this {
436
- return this
437
- }
438
-
439
- /** no-op for compatibility — scan status not available in DO */
440
- scanStatus(): undefined {
441
- return undefined
442
- }
443
-
444
- /** no-op for compatibility */
445
- scanStatusV2(): unknown[] {
446
- return []
447
- }
448
-
449
- /** no-op for compatibility */
450
- scanStatusReset(): void {}
451
-
452
- /** columns() returns column name metadata */
453
- columns(): Array<{ name: string; column: string | null; table: string | null }> {
454
- // execute a dummy query to get column names
455
- const cursor = this.#sql.exec(this.#db._rewriteForSnapshot(this.source))
456
- return cursor.columnNames.map((name) => ({
457
- name,
458
- column: null,
459
- table: null,
460
- }))
461
- }
462
- }
463
-
464
- // -- TransactionFunction --
465
-
466
- type TransactionFunction<F extends (...args: unknown[]) => unknown> = F & {
467
- deferred: F
468
- immediate: F
469
- exclusive: F
470
- }
471
-
472
- // -- Database --
473
-
474
- export class Database {
475
- readonly name: string
476
- #sql: SqlStorageLike
477
- #open: boolean
478
- #inTransaction: boolean
479
- #snapshotTables: Map<string, string> | null
480
- #snapshotPrefix: string
481
- #snapshotCounter: number
482
- #connectionRole: SqliteConnectionRole
483
-
484
- constructor(sqlOrFilename: SqlStorageLike | string, _options?: { readonly?: boolean }) {
485
- if (typeof sqlOrFilename === 'string') {
486
- // when used as a bundler alias for @rocicorp/zero-sqlite3,
487
- // zero-cache passes a file path. look up DO storage from globalThis.
488
- const storage = (globalThis as any).__orez_do_sqlite as SqlStorageLike | undefined
489
- if (!storage) {
490
- throw new SqliteError(
491
- 'sqlite shim: no DO storage on globalThis.__orez_do_sqlite. ' +
492
- 'register DO storage before importing zero-cache.',
493
- 'SQLITE_ERROR'
494
- )
495
- }
496
- this.#sql = storage
497
- this.name = sqlOrFilename
498
- } else {
499
- this.#sql = sqlOrFilename
500
- this.name = ':do-storage:'
501
- }
502
- this.#open = true
503
- this.#inTransaction = false
504
- this.#snapshotTables = null
505
- this.#snapshotPrefix = createSnapshotPrefix()
506
- this.#snapshotCounter = 0
507
- this.#connectionRole = currentConnectionRole()
508
- activeSnapshotPrefixes.add(this.#snapshotPrefix)
509
- cleanupInactiveSnapshotTables(this.#sql)
510
-
511
- // expose storage for StatementRunner to access transactionSync
512
- ;(this as any).__orez_sql = this.#sql
513
- }
514
-
515
- get open(): boolean {
516
- return this.#open
517
- }
518
-
519
- get inTransaction(): boolean {
520
- return this.#inTransaction
521
- }
522
-
523
- // transaction nesting: converts nested BEGIN to SAVEPOINT
524
- // uses shared counter on SqlStorageLike to handle multiple Database instances sharing one sql.js db
525
- //
526
- // CF DO SQLite rejects raw BEGIN/COMMIT/ROLLBACK/SAVEPOINT statements —
527
- // it requires state.storage.transactionSync() instead. when transactionSync
528
- // is available, all transaction control statements become no-ops here.
529
- // DO handles atomicity via its own write coalescing mechanism.
530
- _execTransactionAware(sql: string, sqlStorage: SqlStorageLike): SqlStorageCursor {
531
- const upper = sql.trimStart().toUpperCase()
532
- const shared = sqlStorage as any
533
- const noopCursor: SqlStorageCursor = {
534
- toArray: () => [],
535
- rowsRead: 0,
536
- rowsWritten: 0,
537
- columnNames: [],
538
- }
539
-
540
- // CF DO: all transaction control is no-op (DO coalesces writes automatically)
541
- if (sqlStorage.transactionSync) {
542
- if (upper.startsWith('BEGIN CONCURRENT')) {
543
- if (this.#connectionRole === 'replica-writer') {
544
- shared.__txDepth = (shared.__txDepth || 0) + 1
545
- } else {
546
- this.#beginSnapshot()
547
- }
548
- this.#inTransaction = true
549
- return noopCursor
550
- }
551
- if (upper.startsWith('BEGIN')) {
552
- shared.__txDepth = (shared.__txDepth || 0) + 1
553
- this.#inTransaction = true
554
- return noopCursor
555
- }
556
- if (
557
- upper.startsWith('COMMIT') ||
558
- upper === 'END' ||
559
- upper.startsWith('END ') ||
560
- upper.startsWith('ROLLBACK')
561
- ) {
562
- this.#dropSnapshot()
563
- shared.__txDepth = Math.max(0, (shared.__txDepth || 0) - 1)
564
- if (shared.__txDepth === 0) this.#inTransaction = false
565
- return noopCursor
566
- }
567
- // non-tx statement inside a "transaction" — just execute normally
568
- return sqlStorage.exec(sql)
569
- }
570
-
571
- // non-DO path: real BEGIN/COMMIT/ROLLBACK with SAVEPOINT nesting
572
- if (upper.startsWith('BEGIN')) {
573
- shared.__txDepth = (shared.__txDepth || 0) + 1
574
- if (shared.__txDepth > 1) {
575
- return sqlStorage.exec(`SAVEPOINT _nested_${shared.__txDepth}`)
576
- }
577
- this.#inTransaction = true
578
- return sqlStorage.exec(sql)
579
- }
580
-
581
- if (upper.startsWith('COMMIT') || upper === 'END' || upper.startsWith('END ')) {
582
- if ((shared.__txDepth || 0) > 1) {
583
- const result = sqlStorage.exec(`RELEASE SAVEPOINT _nested_${shared.__txDepth}`)
584
- shared.__txDepth--
585
- return result
586
- }
587
- shared.__txDepth = 0
588
- this.#inTransaction = false
589
- return sqlStorage.exec(sql)
590
- }
591
-
592
- if (upper.startsWith('ROLLBACK')) {
593
- if ((shared.__txDepth || 0) > 1) {
594
- const result = sqlStorage.exec(
595
- `ROLLBACK TO SAVEPOINT _nested_${shared.__txDepth}`
596
- )
597
- shared.__txDepth--
598
- return result
599
- }
600
- shared.__txDepth = 0
601
- this.#inTransaction = false
602
- return sqlStorage.exec(sql)
603
- }
604
-
605
- return sqlStorage.exec(sql)
606
- }
607
-
608
- #beginSnapshot(): void {
609
- this.#dropSnapshot()
610
- const prefix = `${this.#snapshotPrefix}_${++this.#snapshotCounter}`
611
- const tables = new Map<string, string>()
612
- const rows = this.#sql
613
- .exec(
614
- `SELECT name FROM sqlite_master
615
- WHERE type = 'table'
616
- ORDER BY name`
617
- )
618
- .toArray()
619
-
620
- for (const row of rows) {
621
- const name = String(row.name ?? '')
622
- if (!shouldSnapshotTable(name)) continue
623
- const snapshot = snapshotTableName(prefix, name)
624
- this.#sql.exec(
625
- `CREATE TABLE ${quoteIdentifier(snapshot)} AS SELECT * FROM ${quoteIdentifier(name)}`
626
- )
627
- tables.set(name, snapshot)
628
- }
629
-
630
- this.#snapshotTables = tables
631
- }
632
-
633
- #dropSnapshot(): void {
634
- const tables = this.#snapshotTables
635
- if (!tables) return
636
- this.#snapshotTables = null
637
- for (const snapshot of tables.values()) {
638
- try {
639
- this.#sql.exec(`DROP TABLE IF EXISTS ${quoteIdentifier(snapshot)}`)
640
- } catch {}
641
- }
642
- }
643
-
644
- _rewriteForSnapshot(sql: string): string {
645
- if (!this.#snapshotTables) return sql
646
- return rewriteSQLTables(sql, this.#snapshotTables)
647
- }
648
-
649
- /** prepare a statement */
650
- prepare<T = Record<string, SqlStorageValue>>(sql: string): Statement<T> {
651
- if (!this.#open) {
652
- throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE')
653
- }
654
- return new Statement<T>(this.#sql, this, sql)
655
- }
656
-
657
- /**
658
- * execute pragma statements.
659
- *
660
- * DO sqlite supports a subset of pragmas. we handle known ones and
661
- * return sensible defaults for the rest. the `simple` option returns
662
- * just the value instead of an array of objects.
663
- */
664
- pragma(source: string, options?: { simple?: boolean }): unknown {
665
- if (!this.#open) {
666
- throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE')
667
- }
668
-
669
- const trimmed = source.trim().toLowerCase()
670
-
671
- // skip optimize pragma (not supported / can corrupt)
672
- if (trimmed.startsWith('optimize')) {
673
- return options?.simple ? undefined : []
674
- }
675
-
676
- // return sensible defaults for pragmas that DO SQLite may not support
677
- // but that zero-cache's zqlite Database constructor expects
678
- const pragmaDefaults: Record<string, unknown> = {
679
- page_size: [{ page_size: 4096 }],
680
- freelist_count: [{ freelist_count: 0 }],
681
- auto_vacuum: [{ auto_vacuum: 0 }],
682
- page_count: [{ page_count: 0 }],
683
- journal_mode: [{ journal_mode: 'wal2' }],
684
- wal_checkpoint: [{ busy: 0, log: 0, checkpointed: 0 }],
685
- }
686
-
687
- // parse pragma name and value
688
- const eqIndex = source.indexOf('=')
689
- const isSet = eqIndex !== -1
690
-
691
- if (isSet) {
692
- // intercept journal_mode set — sql.js doesn't support wal/wal2, fake it
693
- const pragmaName = source.substring(0, eqIndex).trim().toLowerCase()
694
- const pragmaValue = source
695
- .substring(eqIndex + 1)
696
- .trim()
697
- .replace(/['"]/g, '')
698
- if (pragmaName === 'journal_mode') {
699
- return options?.simple ? pragmaValue : [{ journal_mode: pragmaValue }]
700
- }
701
-
702
- // setting a pragma - execute it and return result
703
- try {
704
- const cursor = this.#sql.exec(`PRAGMA ${source}`)
705
- const rows = cursor.toArray()
706
- return options?.simple ? rows[0]?.[Object.keys(rows[0] ?? {})[0]] : rows
707
- } catch {
708
- // many pragmas are no-ops in DO sqlite - swallow errors
709
- return options?.simple ? undefined : []
710
- }
711
- }
712
-
713
- // reading a pragma — check defaults first for pragmas we intercept
714
- const pragmaName = trimmed.split(/[\s(]/)[0]
715
- const defaultVal = pragmaDefaults[pragmaName]
716
- if (defaultVal) {
717
- if (options?.simple) {
718
- const firstRow = (defaultVal as any[])[0]
719
- return firstRow ? firstRow[Object.keys(firstRow)[0]] : undefined
720
- }
721
- return defaultVal
722
- }
723
-
724
- // try real execution for unknown pragmas
725
- try {
726
- const cursor = this.#sql.exec(`PRAGMA ${source}`)
727
- const rows = cursor.toArray()
728
- if (rows.length > 0) {
729
- if (options?.simple) {
730
- const firstKey = Object.keys(rows[0])[0]
731
- return rows[0][firstKey]
732
- }
733
- return rows
734
- }
735
- } catch {
736
- // sql.js may not support this pragma
737
- }
738
- if (defaultVal) {
739
- if (options?.simple) {
740
- const arr = defaultVal as Record<string, unknown>[]
741
- const firstKey = Object.keys(arr[0])[0]
742
- return arr[0][firstKey]
743
- }
744
- return defaultVal
745
- }
746
- return options?.simple ? undefined : []
747
- }
748
-
749
- /**
750
- * execute one or more sql statements.
751
- * does not return results — used for DDL and multi-statement strings.
752
- */
753
- exec(source: string): this {
754
- if (!this.#open) {
755
- throw new SqliteError('The database connection is not open', 'SQLITE_MISUSE')
756
- }
757
-
758
- // split on semicolons to handle multi-statement strings
759
- // (DO sqlite exec only takes single statements)
760
- const statements = source
761
- .split(';')
762
- .map((s) => s.trim())
763
- .filter((s) => s.length > 0)
764
-
765
- for (const stmt of statements) {
766
- // route transaction control through _execTransactionAware
767
- // so CF DO's transactionSync short-circuit applies
768
- const upper = stmt.trimStart().toUpperCase()
769
- const isTxCmd =
770
- upper.startsWith('BEGIN') ||
771
- upper.startsWith('COMMIT') ||
772
- upper.startsWith('ROLLBACK') ||
773
- upper === 'END' ||
774
- upper.startsWith('END ') ||
775
- upper.startsWith('SAVEPOINT') ||
776
- upper.startsWith('RELEASE ')
777
- if (isTxCmd) {
778
- this._execTransactionAware(stmt, this.#sql)
779
- } else {
780
- this.#sql.exec(this._rewriteForSnapshot(stmt))
781
- }
782
- }
783
-
784
- return this
785
- }
786
-
787
- /**
788
- * create a transaction wrapper function.
789
- *
790
- * returns a function that, when called, wraps `fn` in BEGIN/COMMIT.
791
- * if `fn` throws, issues ROLLBACK instead.
792
- *
793
- * the returned function also has `.deferred()`, `.immediate()`, and
794
- * `.exclusive()` variants.
795
- */
796
- transaction<F extends (...args: unknown[]) => unknown>(fn: F): TransactionFunction<F> {
797
- const self = this
798
-
799
- const wrapInTransaction: F = ((...args: unknown[]) => {
800
- // handle nested transactions — just run fn
801
- if (self.#inTransaction) {
802
- return fn(...args)
803
- }
804
-
805
- // DO SQLite requires transactionSync() — raw BEGIN/COMMIT is rejected.
806
- // fall back to raw SQL only if transactionSync is unavailable.
807
- if (self.#sql.transactionSync) {
808
- return self.#sql.transactionSync(() => {
809
- self.#inTransaction = true
810
- try {
811
- return fn(...args)
812
- } finally {
813
- self.#inTransaction = false
814
- }
815
- })
816
- }
817
-
818
- // fallback for non-DO environments (tests with bedrock-sqlite)
819
- self.#sql.exec('BEGIN')
820
- self.#inTransaction = true
821
- try {
822
- const result = fn(...args)
823
- self.#sql.exec('COMMIT')
824
- self.#inTransaction = false
825
- return result
826
- } catch (err) {
827
- try {
828
- self.#sql.exec('ROLLBACK')
829
- } catch {
830
- // swallow rollback errors
831
- }
832
- self.#inTransaction = false
833
- throw err
834
- }
835
- }) as F
836
-
837
- // all variants use the same transactionSync wrapper on DO
838
- const txn = wrapInTransaction as TransactionFunction<F>
839
- txn.deferred = wrapInTransaction
840
- txn.immediate = wrapInTransaction
841
- txn.exclusive = wrapInTransaction
842
-
843
- return txn
844
- }
845
-
846
- /** close the database connection */
847
- close(): this {
848
- this.#dropSnapshot()
849
- activeSnapshotPrefixes.delete(this.#snapshotPrefix)
850
- this.#open = false
851
- return this
852
- }
853
-
854
- /** no-op for compatibility — unsafe mode not needed in DO */
855
- unsafeMode(_unsafe?: boolean): this {
856
- return this
857
- }
858
-
859
- /** no-op for compatibility */
860
- defaultSafeIntegers(_toggle?: boolean): this {
861
- return this
862
- }
863
- }
864
-
865
- // -- StatementRunner --
866
- // matches zero-cache's db/statements.js StatementRunner interface
867
-
868
- export class StatementRunner {
869
- db: Database
870
- #stmtCache = new Map<string, Statement[]>()
871
- /** DO SqlStorage for transactionSync — set when Database wraps DO storage */
872
- #storage: SqlStorageLike | null = null
873
-
874
- constructor(db: Database) {
875
- this.db = db
876
- // extract the SqlStorageLike from the Database for transactionSync access.
877
- // the Database stores it privately, so we pass it via a well-known property.
878
- this.#storage = (db as any).__orez_sql ?? null
879
- }
880
-
881
- #getStatement(sql: string): Statement {
882
- const cached = this.#stmtCache.get(sql)
883
- if (cached && cached.length > 0) {
884
- return cached.pop()!
885
- }
886
- return this.db.prepare(sql)
887
- }
888
-
889
- #returnStatement(sql: string, stmt: Statement): void {
890
- let arr = this.#stmtCache.get(sql)
891
- if (!arr) {
892
- arr = []
893
- this.#stmtCache.set(sql, arr)
894
- }
895
- arr.push(stmt)
896
- }
897
-
898
- run(sql: string, ...args: unknown[]): RunResult {
899
- const stmt = this.#getStatement(sql)
900
- try {
901
- return stmt.run(...args)
902
- } finally {
903
- this.#returnStatement(sql, stmt)
904
- }
905
- }
906
-
907
- get(sql: string, ...args: unknown[]): unknown {
908
- const stmt = this.#getStatement(sql)
909
- try {
910
- return stmt.get(...args)
911
- } finally {
912
- this.#returnStatement(sql, stmt)
913
- }
914
- }
915
-
916
- all(sql: string, ...args: unknown[]): unknown[] {
917
- const stmt = this.#getStatement(sql)
918
- try {
919
- return stmt.all(...args)
920
- } finally {
921
- this.#returnStatement(sql, stmt)
922
- }
923
- }
924
-
925
- // -- transaction methods --
926
- // DO SQLite rejects raw BEGIN/COMMIT SQL. when transactionSync is
927
- // available, begin() starts a transactionSync block and commit()
928
- // lets it complete. for non-DO environments, falls back to raw SQL.
929
-
930
- #txnResult: RunResult = { changes: 0, lastInsertRowid: 0 }
931
-
932
- begin(): RunResult {
933
- // on DO, transactionSync is closure-based so begin/commit are
934
- // effectively no-ops — the actual transaction wrapping happens
935
- // at the Database.transaction() level or implicitly per-statement.
936
- // we try raw SQL first and swallow the DO rejection.
937
- if (this.#storage?.transactionSync) {
938
- return this.#txnResult
939
- }
940
- return this.run('BEGIN')
941
- }
942
-
943
- beginConcurrent(): RunResult {
944
- if (this.#storage?.transactionSync) {
945
- return this.run('BEGIN CONCURRENT')
946
- }
947
- return this.begin()
948
- }
949
-
950
- beginImmediate(): RunResult {
951
- if (this.#storage?.transactionSync) {
952
- return this.#txnResult
953
- }
954
- return this.run('BEGIN IMMEDIATE')
955
- }
956
-
957
- commit(): RunResult {
958
- if (this.#storage?.transactionSync) {
959
- return this.run('COMMIT')
960
- }
961
- return this.run('COMMIT')
962
- }
963
-
964
- rollback(): RunResult {
965
- if (this.#storage?.transactionSync) {
966
- return this.run('ROLLBACK')
967
- }
968
- return this.run('ROLLBACK')
969
- }
970
- }
971
-
972
- // -- default export --
973
- // matches @rocicorp/zero-sqlite3's default export: the Database class.
974
- // zero-cache's zqlite/src/db.js does:
975
- // import SQLite3Database, { SqliteError } from "@rocicorp/zero-sqlite3"
976
- // new SQLite3Database(path, options)
977
-
978
- export default Database