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,786 +0,0 @@
1
- /**
2
- * sqlite shim tests.
3
- *
4
- * uses a mock SqlStorageLike backed by better-sqlite3 to validate that our
5
- * shim correctly bridges between the better-sqlite3 api and DO SqlStorage.
6
- */
7
-
8
- // @ts-expect-error - CJS module
9
- import BedrockSqlite from 'bedrock-sqlite'
10
- const BetterSqlite3 = BedrockSqlite.Database
11
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
12
-
13
- import {
14
- Database,
15
- Statement,
16
- StatementRunner,
17
- SqliteError,
18
- type SqlStorageLike,
19
- type SqlStorageCursor,
20
- type SqlStorageValue,
21
- } from './sqlite.js'
22
-
23
- // -- mock SqlStorageLike backed by better-sqlite3 --
24
-
25
- function createMockSqlStorage(): SqlStorageLike {
26
- const nativeDb = new BetterSqlite3(':memory:')
27
-
28
- return {
29
- exec(query: string, ...bindings: SqlStorageValue[]): SqlStorageCursor {
30
- const trimmed = query.trim()
31
-
32
- // handle statements that don't return rows
33
- const isWrite =
34
- /^(INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|BEGIN|COMMIT|ROLLBACK|PRAGMA|SAVEPOINT|RELEASE|VACUUM)/i.test(
35
- trimmed
36
- )
37
-
38
- if (isWrite && !trimmed.toUpperCase().startsWith('PRAGMA')) {
39
- const stmt = nativeDb.prepare(query)
40
- const result = stmt.run(...bindings)
41
- return {
42
- toArray: () => [],
43
- get rowsRead() {
44
- return 0
45
- },
46
- get rowsWritten() {
47
- return result.changes
48
- },
49
- get columnNames() {
50
- return []
51
- },
52
- }
53
- }
54
-
55
- // for pragmas — some are SET (contain =), some are GET
56
- if (trimmed.toUpperCase().startsWith('PRAGMA')) {
57
- try {
58
- const stmt = nativeDb.prepare(query)
59
- const rows = stmt.all(...bindings)
60
- return {
61
- toArray: () => rows as Record<string, SqlStorageValue>[],
62
- get rowsRead() {
63
- return rows.length
64
- },
65
- get rowsWritten() {
66
- return 0
67
- },
68
- get columnNames() {
69
- if (rows.length > 0) return Object.keys(rows[0] as object)
70
- return []
71
- },
72
- }
73
- } catch {
74
- // pragma that modifies state (e.g., journal_mode = WAL)
75
- nativeDb.pragma(query.replace(/^PRAGMA\s+/i, ''))
76
- return {
77
- toArray: () => [],
78
- get rowsRead() {
79
- return 0
80
- },
81
- get rowsWritten() {
82
- return 0
83
- },
84
- get columnNames() {
85
- return []
86
- },
87
- }
88
- }
89
- }
90
-
91
- // select / other read queries
92
- const stmt = nativeDb.prepare(query)
93
- const rows = stmt.all(...bindings)
94
- const columns = stmt.columns().map((c: { name: string }) => c.name)
95
- return {
96
- toArray: () => rows as Record<string, SqlStorageValue>[],
97
- get rowsRead() {
98
- return rows.length
99
- },
100
- get rowsWritten() {
101
- return 0
102
- },
103
- get columnNames() {
104
- return columns
105
- },
106
- }
107
- },
108
- // expose native db for cleanup
109
- _nativeDb: nativeDb,
110
- } as SqlStorageLike & { _nativeDb: typeof nativeDb }
111
- }
112
-
113
- // -- tests --
114
-
115
- describe('SqliteError', () => {
116
- it('creates error with message and code', () => {
117
- const err = new SqliteError('table not found', 'SQLITE_ERROR')
118
- expect(err).toBeInstanceOf(Error)
119
- expect(err).toBeInstanceOf(SqliteError)
120
- expect(err.message).toBe('table not found')
121
- expect(err.code).toBe('SQLITE_ERROR')
122
- expect(err.name).toBe('SqliteError')
123
- })
124
-
125
- it('captures stack trace', () => {
126
- const err = new SqliteError('test', 'SQLITE_MISUSE')
127
- expect(err.stack).toBeDefined()
128
- expect(err.stack).toContain('SqliteError')
129
- })
130
- })
131
-
132
- describe('Database', () => {
133
- let mock: SqlStorageLike & { _nativeDb: any }
134
- let db: Database
135
-
136
- beforeEach(() => {
137
- mock = createMockSqlStorage() as SqlStorageLike & { _nativeDb: any }
138
- db = new Database(mock)
139
- })
140
-
141
- afterEach(() => {
142
- db.close()
143
- mock._nativeDb.close()
144
- })
145
-
146
- it('constructs with SqlStorageLike', () => {
147
- expect(db.open).toBe(true)
148
- expect(db.name).toBe(':do-storage:')
149
- expect(db.inTransaction).toBe(false)
150
- })
151
-
152
- it('throws when constructed with a string and no globalThis storage', () => {
153
- // clear any global DO storage
154
- const prev = (globalThis as any).__orez_do_sqlite
155
- delete (globalThis as any).__orez_do_sqlite
156
- try {
157
- expect(() => new Database('/path/to/db' as unknown as SqlStorageLike)).toThrow(
158
- 'no DO storage on globalThis.__orez_do_sqlite'
159
- )
160
- } finally {
161
- if (prev) (globalThis as any).__orez_do_sqlite = prev
162
- }
163
- })
164
-
165
- it('close sets open to false', () => {
166
- expect(db.open).toBe(true)
167
- const ret = db.close()
168
- expect(db.open).toBe(false)
169
- expect(ret).toBe(db) // chainable
170
- })
171
-
172
- it('unsafeMode is a no-op that returns this', () => {
173
- expect(db.unsafeMode(true)).toBe(db)
174
- expect(db.unsafeMode(false)).toBe(db)
175
- expect(db.unsafeMode()).toBe(db)
176
- })
177
-
178
- it('defaultSafeIntegers is a no-op that returns this', () => {
179
- expect(db.defaultSafeIntegers(true)).toBe(db)
180
- })
181
- })
182
-
183
- describe('Database.exec', () => {
184
- let mock: SqlStorageLike & { _nativeDb: any }
185
- let db: Database
186
-
187
- beforeEach(() => {
188
- mock = createMockSqlStorage() as SqlStorageLike & { _nativeDb: any }
189
- db = new Database(mock)
190
- })
191
-
192
- afterEach(() => {
193
- db.close()
194
- mock._nativeDb.close()
195
- })
196
-
197
- it('executes single statement', () => {
198
- const ret = db.exec('CREATE TABLE t (id INTEGER PRIMARY KEY)')
199
- expect(ret).toBe(db) // chainable
200
- })
201
-
202
- it('executes multiple statements separated by semicolons', () => {
203
- db.exec(
204
- 'CREATE TABLE t (id INTEGER PRIMARY KEY); CREATE TABLE t2 (id INTEGER PRIMARY KEY)'
205
- )
206
- // verify both tables exist
207
- const r1 = db
208
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='t'")
209
- .get()
210
- const r2 = db
211
- .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='t2'")
212
- .get()
213
- expect(r1).toEqual({ name: 't' })
214
- expect(r2).toEqual({ name: 't2' })
215
- })
216
-
217
- it('throws on closed database', () => {
218
- db.close()
219
- expect(() => db.exec('SELECT 1')).toThrow('not open')
220
- })
221
- })
222
-
223
- describe('Database.prepare / Statement', () => {
224
- let mock: SqlStorageLike & { _nativeDb: any }
225
- let db: Database
226
-
227
- beforeEach(() => {
228
- mock = createMockSqlStorage() as SqlStorageLike & { _nativeDb: any }
229
- db = new Database(mock)
230
- db.exec('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, val INTEGER)')
231
- })
232
-
233
- afterEach(() => {
234
- db.close()
235
- mock._nativeDb.close()
236
- })
237
-
238
- it('prepare returns a Statement', () => {
239
- const stmt = db.prepare('SELECT 1 as x')
240
- expect(stmt).toBeInstanceOf(Statement)
241
- expect(stmt.source).toBe('SELECT 1 as x')
242
- })
243
-
244
- it('throws prepare on closed db', () => {
245
- db.close()
246
- expect(() => db.prepare('SELECT 1')).toThrow('not open')
247
- })
248
-
249
- it('run executes write and returns RunResult', () => {
250
- const result = db
251
- .prepare('INSERT INTO items (name, val) VALUES (?, ?)')
252
- .run('foo', 42)
253
- expect(result).toHaveProperty('changes')
254
- expect(result).toHaveProperty('lastInsertRowid')
255
- expect(result.changes).toBe(1)
256
- })
257
-
258
- it('get returns first row or undefined', () => {
259
- db.prepare('INSERT INTO items (name, val) VALUES (?, ?)').run('a', 1)
260
- db.prepare('INSERT INTO items (name, val) VALUES (?, ?)').run('b', 2)
261
-
262
- const row = db.prepare('SELECT * FROM items WHERE name = ?').get('a')
263
- expect(row).toEqual({ id: 1, name: 'a', val: 1 })
264
-
265
- const missing = db.prepare('SELECT * FROM items WHERE name = ?').get('zzz')
266
- expect(missing).toBeUndefined()
267
- })
268
-
269
- it('all returns all rows', () => {
270
- db.prepare('INSERT INTO items (name, val) VALUES (?, ?)').run('x', 10)
271
- db.prepare('INSERT INTO items (name, val) VALUES (?, ?)').run('y', 20)
272
-
273
- const rows = db.prepare('SELECT * FROM items ORDER BY id').all()
274
- expect(rows).toEqual([
275
- { id: 1, name: 'x', val: 10 },
276
- { id: 2, name: 'y', val: 20 },
277
- ])
278
- })
279
-
280
- it('all returns empty array when no rows', () => {
281
- const rows = db.prepare('SELECT * FROM items').all()
282
- expect(rows).toEqual([])
283
- })
284
-
285
- it('iterate yields rows lazily', () => {
286
- db.prepare('INSERT INTO items (name, val) VALUES (?, ?)').run('a', 1)
287
- db.prepare('INSERT INTO items (name, val) VALUES (?, ?)').run('b', 2)
288
-
289
- const iter = db.prepare('SELECT * FROM items ORDER BY id').iterate()
290
- const results: unknown[] = []
291
- for (const row of iter) {
292
- results.push(row)
293
- }
294
- expect(results).toEqual([
295
- { id: 1, name: 'a', val: 1 },
296
- { id: 2, name: 'b', val: 2 },
297
- ])
298
- })
299
-
300
- it('safeIntegers is a no-op returning this', () => {
301
- const stmt = db.prepare('SELECT 1 as x')
302
- expect(stmt.safeIntegers(true)).toBe(stmt)
303
- expect(stmt.safeIntegers()).toBe(stmt)
304
- })
305
-
306
- it('statement methods throw on closed db', () => {
307
- const stmt = db.prepare('SELECT 1 as x')
308
- db.close()
309
- expect(() => stmt.run()).toThrow('not open')
310
- expect(() => stmt.get()).toThrow('not open')
311
- expect(() => stmt.all()).toThrow('not open')
312
- })
313
- })
314
-
315
- describe('Database.pragma', () => {
316
- let mock: SqlStorageLike & { _nativeDb: any }
317
- let db: Database
318
-
319
- beforeEach(() => {
320
- mock = createMockSqlStorage() as SqlStorageLike & { _nativeDb: any }
321
- db = new Database(mock)
322
- })
323
-
324
- afterEach(() => {
325
- mock._nativeDb.close()
326
- })
327
-
328
- it('reads a pragma and returns array of objects', () => {
329
- const result = db.pragma('journal_mode')
330
- expect(Array.isArray(result)).toBe(true)
331
- expect((result as unknown[]).length).toBeGreaterThan(0)
332
- })
333
-
334
- it('reads pragma with simple option', () => {
335
- const result = db.pragma('journal_mode', { simple: true })
336
- // should return a scalar value, not an array
337
- expect(Array.isArray(result)).toBe(false)
338
- expect(typeof result).toBe('string')
339
- })
340
-
341
- it('sets a pragma', () => {
342
- const result = db.pragma('cache_size = 2000')
343
- // set pragmas return the new value or empty
344
- expect(Array.isArray(result)).toBe(true)
345
- })
346
-
347
- it('skips optimize pragma', () => {
348
- const result = db.pragma('optimize')
349
- expect(result).toEqual([])
350
-
351
- const simple = db.pragma('optimize', { simple: true })
352
- expect(simple).toBeUndefined()
353
- })
354
-
355
- it('throws on closed db', () => {
356
- db.close()
357
- expect(() => db.pragma('journal_mode')).toThrow('not open')
358
- })
359
- })
360
-
361
- describe('Database.transaction', () => {
362
- let mock: SqlStorageLike & { _nativeDb: any }
363
- let db: Database
364
-
365
- beforeEach(() => {
366
- mock = createMockSqlStorage() as SqlStorageLike & { _nativeDb: any }
367
- db = new Database(mock)
368
- db.exec('CREATE TABLE t (id INTEGER PRIMARY KEY)')
369
- })
370
-
371
- afterEach(() => {
372
- mock._nativeDb.close()
373
- })
374
-
375
- it('commits on success', () => {
376
- const txn = db.transaction(() => {
377
- db.prepare('INSERT INTO t VALUES (1)').run()
378
- db.prepare('INSERT INTO t VALUES (2)').run()
379
- })
380
-
381
- txn()
382
-
383
- const rows = db.prepare('SELECT * FROM t').all()
384
- expect(rows).toEqual([{ id: 1 }, { id: 2 }])
385
- })
386
-
387
- it('rolls back on error', () => {
388
- const txn = db.transaction(() => {
389
- db.prepare('INSERT INTO t VALUES (1)').run()
390
- throw new Error('boom')
391
- })
392
-
393
- expect(() => txn()).toThrow('boom')
394
-
395
- const rows = db.prepare('SELECT * FROM t').all()
396
- expect(rows).toEqual([])
397
- })
398
-
399
- it('inTransaction reflects state', () => {
400
- expect(db.inTransaction).toBe(false)
401
-
402
- db.transaction(() => {
403
- expect(db.inTransaction).toBe(true)
404
- })()
405
-
406
- expect(db.inTransaction).toBe(false)
407
- })
408
-
409
- it('inTransaction is false after rollback', () => {
410
- try {
411
- db.transaction(() => {
412
- throw new Error('fail')
413
- })()
414
- } catch {}
415
-
416
- expect(db.inTransaction).toBe(false)
417
- })
418
-
419
- it('has deferred/immediate/exclusive variants', () => {
420
- const txn = db.transaction(() => {
421
- db.prepare('INSERT INTO t VALUES (99)').run()
422
- })
423
-
424
- expect(typeof txn.deferred).toBe('function')
425
- expect(typeof txn.immediate).toBe('function')
426
- expect(typeof txn.exclusive).toBe('function')
427
- })
428
-
429
- it('immediate variant works', () => {
430
- const txn = db.transaction(() => {
431
- db.prepare('INSERT INTO t VALUES (42)').run()
432
- })
433
-
434
- txn.immediate()
435
-
436
- const rows = db.prepare('SELECT * FROM t').all()
437
- expect(rows).toEqual([{ id: 42 }])
438
- })
439
-
440
- it('nested transaction calls run fn without extra BEGIN', () => {
441
- const inner = db.transaction(() => {
442
- db.prepare('INSERT INTO t VALUES (2)').run()
443
- })
444
-
445
- const outer = db.transaction(() => {
446
- db.prepare('INSERT INTO t VALUES (1)').run()
447
- inner() // should NOT issue another BEGIN
448
- db.prepare('INSERT INTO t VALUES (3)').run()
449
- })
450
-
451
- outer()
452
-
453
- const rows = db.prepare('SELECT * FROM t ORDER BY id').all()
454
- expect(rows).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }])
455
- })
456
-
457
- it('passes arguments through to wrapped function', () => {
458
- const txn = db.transaction((id: unknown, val: unknown) => {
459
- db.prepare('INSERT INTO t VALUES (?)').run(id)
460
- return val
461
- })
462
-
463
- const result = txn(7, 'hello')
464
- expect(result).toBe('hello')
465
- expect(db.prepare('SELECT * FROM t').all()).toEqual([{ id: 7 }])
466
- })
467
- })
468
-
469
- describe('StatementRunner', () => {
470
- let mock: SqlStorageLike & { _nativeDb: any }
471
- let db: Database
472
- let runner: StatementRunner
473
-
474
- beforeEach(() => {
475
- mock = createMockSqlStorage() as SqlStorageLike & { _nativeDb: any }
476
- db = new Database(mock)
477
- db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY, name TEXT)')
478
- runner = new StatementRunner(db)
479
- })
480
-
481
- afterEach(() => {
482
- mock._nativeDb.close()
483
- })
484
-
485
- it('run inserts data', () => {
486
- const result = runner.run('INSERT INTO foo (name) VALUES (?)', 'bar')
487
- expect(result).toHaveProperty('changes')
488
- expect(result.changes).toBe(1)
489
- })
490
-
491
- it('get returns single row', () => {
492
- runner.run('INSERT INTO foo (name) VALUES (?)', 'baz')
493
- const row = runner.get('SELECT * FROM foo WHERE name = ?', 'baz')
494
- expect(row).toEqual({ id: 1, name: 'baz' })
495
- })
496
-
497
- it('get returns undefined for no match', () => {
498
- const row = runner.get('SELECT * FROM foo WHERE name = ?', 'nope')
499
- expect(row).toBeUndefined()
500
- })
501
-
502
- it('all returns all rows', () => {
503
- runner.run('INSERT INTO foo (name) VALUES (?)', 'a')
504
- runner.run('INSERT INTO foo (name) VALUES (?)', 'b')
505
- const rows = runner.all('SELECT * FROM foo ORDER BY id')
506
- expect(rows).toEqual([
507
- { id: 1, name: 'a' },
508
- { id: 2, name: 'b' },
509
- ])
510
- })
511
-
512
- it('begin/commit transaction', () => {
513
- runner.begin()
514
- runner.run('INSERT INTO foo (name) VALUES (?)', 'x')
515
- runner.run('INSERT INTO foo (name) VALUES (?)', 'y')
516
- runner.commit()
517
-
518
- expect(runner.all('SELECT * FROM foo ORDER BY id')).toEqual([
519
- { id: 1, name: 'x' },
520
- { id: 2, name: 'y' },
521
- ])
522
- })
523
-
524
- it('begin/rollback discards changes', () => {
525
- runner.begin()
526
- runner.run('INSERT INTO foo (name) VALUES (?)', 'x')
527
- runner.rollback()
528
-
529
- expect(runner.all('SELECT * FROM foo')).toEqual([])
530
- })
531
-
532
- it('beginConcurrent maps to regular BEGIN', () => {
533
- runner.beginConcurrent()
534
- runner.run('INSERT INTO foo (name) VALUES (?)', 'concurrent')
535
- runner.commit()
536
-
537
- expect(runner.all('SELECT * FROM foo')).toEqual([{ id: 1, name: 'concurrent' }])
538
- })
539
-
540
- it('beginImmediate works', () => {
541
- runner.beginImmediate()
542
- runner.run('INSERT INTO foo (name) VALUES (?)', 'immediate')
543
- runner.commit()
544
-
545
- expect(runner.all('SELECT * FROM foo')).toEqual([{ id: 1, name: 'immediate' }])
546
- })
547
-
548
- it('caches prepared statements', () => {
549
- // run same SQL multiple times — statements should be reused
550
- runner.run('INSERT INTO foo (name) VALUES (?)', 'a')
551
- runner.run('INSERT INTO foo (name) VALUES (?)', 'b')
552
- runner.run('INSERT INTO foo (name) VALUES (?)', 'c')
553
-
554
- expect(runner.all('SELECT count(*) as c FROM foo')).toEqual([{ c: 3 }])
555
- })
556
- })
557
-
558
- describe('DO snapshot transactions', () => {
559
- let mock: SqlStorageLike & { _nativeDb: any; transactionSync: <T>(fn: () => T) => T }
560
- let live: Database
561
- let snapshot: Database
562
-
563
- beforeEach(() => {
564
- mock = createMockSqlStorage() as SqlStorageLike & {
565
- _nativeDb: any
566
- transactionSync: <T>(fn: () => T) => T
567
- }
568
- mock.transactionSync = (fn) => fn()
569
- live = new Database(mock)
570
- snapshot = new Database(mock)
571
- live.exec('CREATE TABLE todo (id TEXT PRIMARY KEY, title TEXT, _0_version TEXT)')
572
- live.prepare('INSERT INTO todo VALUES (?, ?, ?)').run('1', 'old', '01')
573
- })
574
-
575
- afterEach(() => {
576
- live.close()
577
- snapshot.close()
578
- mock._nativeDb.close()
579
- })
580
-
581
- it('keeps BEGIN CONCURRENT reads stable until rollback', () => {
582
- snapshot.prepare('BEGIN CONCURRENT').run()
583
- expect(snapshot.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
584
- title: 'old',
585
- })
586
-
587
- live
588
- .prepare('UPDATE todo SET title = ?, _0_version = ? WHERE id = ?')
589
- .run('new', '02', '1')
590
-
591
- expect(live.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
592
- title: 'new',
593
- })
594
- expect(snapshot.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
595
- title: 'old',
596
- })
597
-
598
- snapshot.prepare('ROLLBACK').run()
599
- expect(snapshot.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
600
- title: 'new',
601
- })
602
- })
603
-
604
- it('hides snapshot tables from sqlite catalog queries', () => {
605
- snapshot.prepare('BEGIN CONCURRENT').run()
606
-
607
- expect(
608
- live
609
- .prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name")
610
- .all()
611
- ).toEqual([{ name: 'todo' }])
612
- })
613
-
614
- it('does not rewrite table names inside string literals during snapshots', () => {
615
- live.exec('CREATE TABLE log (id TEXT PRIMARY KEY, table_name TEXT, _0_version TEXT)')
616
- live.prepare('INSERT INTO log VALUES (?, ?, ?)').run('1', 'todo', '01')
617
- live.prepare('INSERT INTO log VALUES (?, ?, ?)').run('2', '"todo"', '02')
618
-
619
- snapshot.prepare('BEGIN CONCURRENT').run()
620
-
621
- expect(
622
- snapshot.prepare("SELECT table_name FROM log WHERE table_name = 'todo'").get()
623
- ).toEqual({ table_name: 'todo' })
624
- expect(
625
- snapshot.prepare('SELECT table_name FROM log WHERE table_name = ?').get('"todo"')
626
- ).toEqual({ table_name: '"todo"' })
627
- })
628
-
629
- it('cleans inactive snapshot tables when opening a database', () => {
630
- mock.exec('CREATE TABLE _orez_snapshot_1_todo AS SELECT * FROM todo')
631
- expect(
632
- mock
633
- .exec("SELECT name FROM sqlite_master WHERE name = '_orez_snapshot_1_todo'")
634
- .toArray()
635
- ).toEqual([{ name: '_orez_snapshot_1_todo' }])
636
-
637
- const reopened = new Database(mock)
638
- try {
639
- expect(
640
- mock
641
- .exec("SELECT name FROM sqlite_master WHERE name = '_orez_snapshot_1_todo'")
642
- .toArray()
643
- ).toEqual([])
644
- } finally {
645
- reopened.close()
646
- }
647
- })
648
-
649
- it('does not remove another open connection snapshot', () => {
650
- snapshot.prepare('BEGIN CONCURRENT').run()
651
- const snapshotTables = mock
652
- .exec("SELECT name FROM sqlite_master WHERE name LIKE '_orez_snapshot_%'")
653
- .toArray()
654
- expect(snapshotTables).toHaveLength(1)
655
-
656
- const other = new Database(mock)
657
- try {
658
- live
659
- .prepare('UPDATE todo SET title = ?, _0_version = ? WHERE id = ?')
660
- .run('new', '02', '1')
661
-
662
- expect(snapshot.prepare('SELECT title FROM todo WHERE id = ?').get('1')).toEqual({
663
- title: 'old',
664
- })
665
- } finally {
666
- other.close()
667
- }
668
- })
669
-
670
- it('persists BEGIN CONCURRENT writes for the zero-cache replica writer', () => {
671
- const globalObject = globalThis as any
672
- const previousRole = globalObject.__orez_zero_sqlite_role
673
- globalObject.__orez_zero_sqlite_role = 'replica-writer'
674
- let writer: Database
675
- try {
676
- writer = new Database(mock)
677
- } finally {
678
- if (previousRole === undefined) {
679
- delete globalObject.__orez_zero_sqlite_role
680
- } else {
681
- globalObject.__orez_zero_sqlite_role = previousRole
682
- }
683
- }
684
-
685
- writer.prepare('BEGIN CONCURRENT').run()
686
- writer.prepare('INSERT INTO todo VALUES (?, ?, ?)').run('2', 'writer row', '02')
687
- writer.prepare('COMMIT').run()
688
-
689
- expect(live.prepare('SELECT title FROM todo WHERE id = ?').get('2')).toEqual({
690
- title: 'writer row',
691
- })
692
- expect(
693
- live
694
- .prepare("SELECT name FROM sqlite_master WHERE name LIKE '_orez_snapshot_%'")
695
- .all()
696
- ).toEqual([])
697
- })
698
- })
699
-
700
- describe('StatementRunner: zero-cache replicator pattern', () => {
701
- let mock: SqlStorageLike & { _nativeDb: any }
702
- let db: Database
703
- let runner: StatementRunner
704
-
705
- beforeEach(() => {
706
- mock = createMockSqlStorage() as SqlStorageLike & { _nativeDb: any }
707
- db = new Database(mock)
708
- db.exec(`
709
- CREATE TABLE issues (
710
- issueID INTEGER PRIMARY KEY,
711
- title TEXT,
712
- _0_version TEXT
713
- )
714
- `)
715
- runner = new StatementRunner(db)
716
- })
717
-
718
- afterEach(() => {
719
- mock._nativeDb.close()
720
- })
721
-
722
- it('simulates zero-cache batch processing', () => {
723
- // batch 1
724
- runner.begin()
725
- runner.run(
726
- 'INSERT INTO issues (issueID, title, _0_version) VALUES (?, ?, ?)',
727
- 1,
728
- 'bug',
729
- '01'
730
- )
731
- runner.run(
732
- 'INSERT INTO issues (issueID, title, _0_version) VALUES (?, ?, ?)',
733
- 2,
734
- 'feat',
735
- '01'
736
- )
737
- runner.commit()
738
-
739
- expect(runner.all('SELECT * FROM issues ORDER BY issueID')).toEqual([
740
- { issueID: 1, title: 'bug', _0_version: '01' },
741
- { issueID: 2, title: 'feat', _0_version: '01' },
742
- ])
743
-
744
- // batch 2
745
- runner.begin()
746
- runner.run(
747
- 'INSERT OR REPLACE INTO issues (issueID, title, _0_version) VALUES (?, ?, ?)',
748
- 1,
749
- 'bug fix',
750
- '02'
751
- )
752
- runner.commit()
753
-
754
- expect(runner.get('SELECT title FROM issues WHERE issueID = ?', 1)).toEqual({
755
- title: 'bug fix',
756
- })
757
- })
758
-
759
- it('simulates rollback on conflict', () => {
760
- runner.begin()
761
- runner.run(
762
- 'INSERT INTO issues (issueID, title, _0_version) VALUES (?, ?, ?)',
763
- 1,
764
- 'ok',
765
- '01'
766
- )
767
- runner.commit()
768
-
769
- runner.begin()
770
- try {
771
- runner.run(
772
- 'INSERT INTO issues (issueID, title, _0_version) VALUES (?, ?, ?)',
773
- 1,
774
- 'dupe',
775
- '02'
776
- )
777
- } catch {
778
- runner.rollback()
779
- }
780
-
781
- // original row should still be there
782
- expect(runner.get('SELECT title FROM issues WHERE issueID = ?', 1)).toEqual({
783
- title: 'ok',
784
- })
785
- })
786
- })