@livestore/sqlite-wasm 0.4.0-dev.1 → 0.4.0-dev.10

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 (80) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser/mod.d.ts +1 -0
  3. package/dist/browser/mod.d.ts.map +1 -1
  4. package/dist/browser/mod.js.map +1 -1
  5. package/dist/browser/opfs/AccessHandlePoolVFS.d.ts +17 -0
  6. package/dist/browser/opfs/AccessHandlePoolVFS.d.ts.map +1 -1
  7. package/dist/browser/opfs/AccessHandlePoolVFS.js +72 -1
  8. package/dist/browser/opfs/AccessHandlePoolVFS.js.map +1 -1
  9. package/dist/cf/BlockManager.d.ts +61 -0
  10. package/dist/cf/BlockManager.d.ts.map +1 -0
  11. package/dist/cf/BlockManager.js +157 -0
  12. package/dist/cf/BlockManager.js.map +1 -0
  13. package/dist/cf/CloudflareSqlVFS.d.ts +51 -0
  14. package/dist/cf/CloudflareSqlVFS.d.ts.map +1 -0
  15. package/dist/cf/CloudflareSqlVFS.js +351 -0
  16. package/dist/cf/CloudflareSqlVFS.js.map +1 -0
  17. package/dist/cf/CloudflareWorkerVFS.d.ts +72 -0
  18. package/dist/cf/CloudflareWorkerVFS.d.ts.map +1 -0
  19. package/dist/cf/CloudflareWorkerVFS.js +552 -0
  20. package/dist/cf/CloudflareWorkerVFS.js.map +1 -0
  21. package/dist/cf/mod.d.ts +43 -0
  22. package/dist/cf/mod.d.ts.map +1 -0
  23. package/dist/cf/mod.js +74 -0
  24. package/dist/cf/mod.js.map +1 -0
  25. package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.d.ts +2 -0
  26. package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.d.ts.map +1 -0
  27. package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.js +314 -0
  28. package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.js.map +1 -0
  29. package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.d.ts +2 -0
  30. package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.d.ts.map +1 -0
  31. package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.js +266 -0
  32. package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.js.map +1 -0
  33. package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.d.ts +2 -0
  34. package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.d.ts.map +1 -0
  35. package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.js +462 -0
  36. package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.js.map +1 -0
  37. package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.d.ts +2 -0
  38. package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.d.ts.map +1 -0
  39. package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.js +334 -0
  40. package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.js.map +1 -0
  41. package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.d.ts +2 -0
  42. package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.d.ts.map +1 -0
  43. package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.js +354 -0
  44. package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.js.map +1 -0
  45. package/dist/load-wasm/mod.node.d.ts.map +1 -1
  46. package/dist/load-wasm/mod.node.js +1 -2
  47. package/dist/load-wasm/mod.node.js.map +1 -1
  48. package/dist/load-wasm/mod.workerd.d.ts +2 -0
  49. package/dist/load-wasm/mod.workerd.d.ts.map +1 -0
  50. package/dist/load-wasm/mod.workerd.js +26 -0
  51. package/dist/load-wasm/mod.workerd.js.map +1 -0
  52. package/dist/make-sqlite-db.d.ts +1 -0
  53. package/dist/make-sqlite-db.d.ts.map +1 -1
  54. package/dist/make-sqlite-db.js +28 -4
  55. package/dist/make-sqlite-db.js.map +1 -1
  56. package/dist/node/NodeFS.d.ts +1 -2
  57. package/dist/node/NodeFS.d.ts.map +1 -1
  58. package/dist/node/NodeFS.js +1 -6
  59. package/dist/node/NodeFS.js.map +1 -1
  60. package/dist/node/mod.js +3 -8
  61. package/dist/node/mod.js.map +1 -1
  62. package/package.json +21 -8
  63. package/src/browser/mod.ts +1 -0
  64. package/src/browser/opfs/AccessHandlePoolVFS.ts +79 -1
  65. package/src/cf/BlockManager.ts +225 -0
  66. package/src/cf/CloudflareSqlVFS.ts +450 -0
  67. package/src/cf/CloudflareWorkerVFS.ts +664 -0
  68. package/src/cf/README.md +60 -0
  69. package/src/cf/mod.ts +143 -0
  70. package/src/cf/test/README.md +224 -0
  71. package/src/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.ts +389 -0
  72. package/src/cf/test/async-storage/cloudflare-worker-vfs-core.test.ts +322 -0
  73. package/src/cf/test/async-storage/cloudflare-worker-vfs-integration.test.ts +585 -0
  74. package/src/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.ts +403 -0
  75. package/src/cf/test/sql/cloudflare-sql-vfs-core.test.ts +433 -0
  76. package/src/load-wasm/mod.node.ts +1 -2
  77. package/src/load-wasm/mod.workerd.ts +26 -0
  78. package/src/make-sqlite-db.ts +38 -4
  79. package/src/node/NodeFS.ts +1 -9
  80. package/src/node/mod.ts +3 -10
@@ -0,0 +1,585 @@
1
+ /// <reference types="vitest/globals" />
2
+
3
+ import type { CfTypes } from '@livestore/common-cf'
4
+ import * as VFS from '@livestore/wa-sqlite/src/VFS.js'
5
+ import { beforeEach, describe, expect, it } from 'vitest'
6
+ import { CloudflareWorkerVFS } from '../../mod.ts'
7
+
8
+ describe('CloudflareWorkerVFS - Integration Tests', () => {
9
+ let vfs: CloudflareWorkerVFS
10
+ let mockStorage: CfTypes.DurableObjectStorage
11
+ let storageData: Map<string, any>
12
+ let storageOperations: string[]
13
+
14
+ beforeEach(async () => {
15
+ storageData = new Map<string, any>()
16
+ storageOperations = []
17
+
18
+ mockStorage = {
19
+ get: (async (_key: string | string[]) => {
20
+ if (Array.isArray(_key)) {
21
+ storageOperations.push(`get-batch: ${_key.length} keys`)
22
+ const result = new Map()
23
+ for (const k of _key) {
24
+ const value = storageData.get(k)
25
+ if (value !== undefined) {
26
+ result.set(k, value)
27
+ }
28
+ }
29
+ return result
30
+ } else {
31
+ storageOperations.push(`get: ${_key}`)
32
+ return storageData.get(_key)
33
+ }
34
+ }) as CfTypes.DurableObjectStorage['get'],
35
+
36
+ put: async (_key: string | Record<string, any>, _value?: any) => {
37
+ if (typeof _key === 'string') {
38
+ storageOperations.push(`put: ${_key}`)
39
+ storageData.set(_key, _value)
40
+ } else {
41
+ storageOperations.push(`put-batch: ${Object.keys(_key).length} keys`)
42
+ for (const [k, v] of Object.entries(_key)) {
43
+ storageData.set(k, v)
44
+ }
45
+ }
46
+ },
47
+
48
+ delete: (async (_key: string | string[]) => {
49
+ if (Array.isArray(_key)) {
50
+ storageOperations.push(`delete-batch: ${_key.length} keys`)
51
+ let count = 0
52
+ for (const k of _key) {
53
+ if (storageData.delete(k)) count++
54
+ }
55
+ return count
56
+ } else {
57
+ storageOperations.push(`delete: ${_key}`)
58
+ return storageData.delete(_key)
59
+ }
60
+ }) as CfTypes.DurableObjectStorage['delete'],
61
+
62
+ list: async () => {
63
+ storageOperations.push('list')
64
+ return new Map(storageData)
65
+ },
66
+
67
+ sync: async () => {
68
+ storageOperations.push('sync')
69
+ },
70
+
71
+ transactionSync: (fn: () => any) => {
72
+ storageOperations.push('transactionSync')
73
+ return fn()
74
+ },
75
+
76
+ deleteAll: async () => {
77
+ storageOperations.push('deleteAll')
78
+ storageData.clear()
79
+ },
80
+
81
+ transaction: async (fn: (txn: any) => Promise<any>) => {
82
+ storageOperations.push('transaction')
83
+ return await fn({} as any)
84
+ },
85
+
86
+ getCurrentBookmark: async () => {
87
+ storageOperations.push('getCurrentBookmark')
88
+ return ''
89
+ },
90
+
91
+ getBookmarkForTime: async (_time: number | Date) => {
92
+ storageOperations.push('getBookmarkForTime')
93
+ return ''
94
+ },
95
+
96
+ onNextSessionRestoreBookmark: async (_bookmark: string) => {
97
+ storageOperations.push('onNextSessionRestoreBookmark')
98
+ return ''
99
+ },
100
+
101
+ getAlarm: async () => null,
102
+ setAlarm: async (_timestamp: number | Date) => {},
103
+ deleteAlarm: async () => {},
104
+ sql: {} as any,
105
+ kv: {
106
+ get: (key: string) => {
107
+ storageOperations.push(`kv-get: ${key}`)
108
+ return storageData.get(key)
109
+ },
110
+ put: (key: string, value: unknown) => {
111
+ storageOperations.push(`kv-put: ${key}`)
112
+ storageData.set(key, value)
113
+ },
114
+ delete: (key: string) => {
115
+ storageOperations.push(`kv-delete: ${key}`)
116
+ return storageData.delete(key)
117
+ },
118
+ list: () => {
119
+ storageOperations.push('kv-list')
120
+ return storageData.entries()
121
+ },
122
+ },
123
+ }
124
+
125
+ vfs = new CloudflareWorkerVFS('test-integration-vfs', mockStorage, {})
126
+ await vfs.isReady()
127
+ })
128
+
129
+ describe('Storage Integration', () => {
130
+ it('should integrate properly with DurableObjectStorage API', async () => {
131
+ const path = '/test/storage-integration.db'
132
+ const fileId = 1
133
+ const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
134
+ const pOutFlags = new DataView(new ArrayBuffer(4))
135
+
136
+ storageOperations.length = 0
137
+
138
+ vfs.jOpen(path, fileId, flags, pOutFlags)
139
+
140
+ // Write data that will trigger storage operations
141
+ const testData = new TextEncoder().encode('Storage integration test')
142
+ expect(vfs.jWrite(fileId, testData, 0)).toBe(VFS.SQLITE_OK)
143
+
144
+ // Sync to ensure storage operations occur
145
+ expect(vfs.jSync(fileId, VFS.SQLITE_SYNC_NORMAL)).toBe(VFS.SQLITE_OK)
146
+ expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
147
+
148
+ // Wait for async operations
149
+ await new Promise((resolve) => setTimeout(resolve, 20))
150
+
151
+ // Verify storage operations occurred
152
+ expect(storageOperations.length).toBeGreaterThan(0)
153
+
154
+ // Verify metadata and chunk keys exist in storage
155
+ const metadataKey = `file:${path}:meta`
156
+ const chunkKey = `file:${path}:0`
157
+
158
+ expect(storageData.has(metadataKey)).toBe(true)
159
+ expect(storageData.has(chunkKey)).toBe(true)
160
+
161
+ // Verify stored data integrity
162
+ const metadata = storageData.get(metadataKey)
163
+ expect(metadata.size).toBe(testData.length)
164
+ expect(metadata.flags).toBe(flags)
165
+
166
+ const chunk = storageData.get(chunkKey)
167
+ expect(chunk.slice(0, testData.length)).toEqual(testData)
168
+ })
169
+
170
+ it('should handle storage key collisions gracefully', async () => {
171
+ const paths = ['/test/path:with:colons.db', '/test/path_with_colons.db', '/test/pathwithcolons.db']
172
+
173
+ for (let i = 0; i < paths.length; i++) {
174
+ const fileId = i + 1
175
+ const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
176
+ const pOutFlags = new DataView(new ArrayBuffer(4))
177
+
178
+ expect(vfs.jOpen(paths[i] ?? '', fileId, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
179
+
180
+ const testData = new TextEncoder().encode(`Data for file ${i}`)
181
+ expect(vfs.jWrite(fileId, testData, 0)).toBe(VFS.SQLITE_OK)
182
+
183
+ expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
184
+ }
185
+
186
+ // Wait for async operations
187
+ await new Promise((resolve) => setTimeout(resolve, 20))
188
+
189
+ // Verify all files have separate storage entries
190
+ for (const path of paths) {
191
+ const metadataKey = `file:${path}:meta`
192
+ expect(storageData.has(metadataKey)).toBe(true)
193
+ }
194
+
195
+ // Verify no data corruption between files
196
+ for (let i = 0; i < paths.length; i++) {
197
+ const fileId = i + 1
198
+ const flags = VFS.SQLITE_OPEN_READWRITE
199
+ const pOutFlags = new DataView(new ArrayBuffer(4))
200
+
201
+ expect(vfs.jOpen(paths[i] ?? '', fileId, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
202
+
203
+ const expectedData = new TextEncoder().encode(`Data for file ${i}`)
204
+ const readData = new Uint8Array(expectedData.length)
205
+ expect(vfs.jRead(fileId, readData, 0)).toBe(VFS.SQLITE_OK)
206
+ expect(readData).toEqual(expectedData)
207
+
208
+ expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
209
+ }
210
+ })
211
+
212
+ it('should handle storage size limits correctly', async () => {
213
+ const path = '/test/size-limits.db'
214
+ const fileId = 1
215
+ const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
216
+ const pOutFlags = new DataView(new ArrayBuffer(4))
217
+
218
+ vfs.jOpen(path, fileId, flags, pOutFlags)
219
+
220
+ // Write data that approaches DurableObjectStorage limits (128 KiB per value)
221
+ const chunkSize = 64 * 1024 // VFS chunk size
222
+ const testData = new Uint8Array(chunkSize)
223
+ testData.fill(0xaa)
224
+
225
+ expect(vfs.jWrite(fileId, testData, 0)).toBe(VFS.SQLITE_OK)
226
+ expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
227
+
228
+ // Wait for async operations
229
+ await new Promise((resolve) => setTimeout(resolve, 10))
230
+
231
+ // Verify chunk was stored correctly
232
+ const chunkKey = `file:${path}:0`
233
+ expect(storageData.has(chunkKey)).toBe(true)
234
+
235
+ const storedChunk = storageData.get(chunkKey)
236
+ expect(storedChunk.length).toBe(chunkSize)
237
+ expect(storedChunk.length).toBeLessThanOrEqual(128 * 1024) // Within DO Storage limit
238
+ })
239
+
240
+ it('should handle batch operations efficiently', async () => {
241
+ const path = '/test/batch-operations.db'
242
+ const fileId = 1
243
+ const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
244
+ const pOutFlags = new DataView(new ArrayBuffer(4))
245
+
246
+ vfs.jOpen(path, fileId, flags, pOutFlags)
247
+
248
+ // Write data that spans multiple chunks
249
+ const chunkSize = 64 * 1024
250
+ const numChunks = 3
251
+ const totalData = new Uint8Array(chunkSize * numChunks)
252
+
253
+ for (let i = 0; i < totalData.length; i++) {
254
+ totalData[i] = (i * 3) % 256
255
+ }
256
+
257
+ storageOperations.length = 0
258
+ expect(vfs.jWrite(fileId, totalData, 0)).toBe(VFS.SQLITE_OK)
259
+ expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
260
+
261
+ // Wait for async operations
262
+ await new Promise((resolve) => setTimeout(resolve, 20))
263
+
264
+ // Verify storage operations were batched efficiently
265
+ expect(storageOperations.length).toBeGreaterThan(0)
266
+
267
+ // Verify all chunks were stored
268
+ for (let i = 0; i < numChunks; i++) {
269
+ const chunkKey = `file:${path}:${i}`
270
+ expect(storageData.has(chunkKey)).toBe(true)
271
+ }
272
+
273
+ // Verify data integrity
274
+ vfs.jOpen(path, fileId, flags, pOutFlags)
275
+ const readData = new Uint8Array(totalData.length)
276
+ expect(vfs.jRead(fileId, readData, 0)).toBe(VFS.SQLITE_OK)
277
+ expect(readData).toEqual(totalData)
278
+ expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
279
+ })
280
+ })
281
+
282
+ describe('SQLite Integration', () => {
283
+ it('should handle SQLite database file format correctly', async () => {
284
+ const path = '/test/sqlite-format.db'
285
+ const fileId = 1
286
+ const flags = VFS.SQLITE_OPEN_MAIN_DB | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
287
+ const pOutFlags = new DataView(new ArrayBuffer(4))
288
+
289
+ expect(vfs.jOpen(path, fileId, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
290
+
291
+ // Write SQLite header
292
+ const sqliteHeader = new TextEncoder().encode('SQLite format 3\0')
293
+ expect(vfs.jWrite(fileId, sqliteHeader, 0)).toBe(VFS.SQLITE_OK)
294
+
295
+ // Write page size (typical SQLite page size is 4096)
296
+ const pageSize = new DataView(new ArrayBuffer(2))
297
+ pageSize.setUint16(0, 4096, false) // Big-endian as per SQLite format
298
+ const pageSizeBytes = new Uint8Array(pageSize.buffer)
299
+ expect(vfs.jWrite(fileId, pageSizeBytes, 16)).toBe(VFS.SQLITE_OK)
300
+
301
+ // Read back and verify header
302
+ const readHeader = new Uint8Array(sqliteHeader.length)
303
+ expect(vfs.jRead(fileId, readHeader, 0)).toBe(VFS.SQLITE_OK)
304
+ expect(readHeader).toEqual(sqliteHeader)
305
+
306
+ // Read back and verify page size
307
+ const readPageSize = new Uint8Array(2)
308
+ expect(vfs.jRead(fileId, readPageSize, 16)).toBe(VFS.SQLITE_OK)
309
+ expect(readPageSize).toEqual(pageSizeBytes)
310
+
311
+ expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
312
+ })
313
+
314
+ it('should handle SQLite page-based I/O operations', async () => {
315
+ const path = '/test/sqlite-pages.db'
316
+ const fileId = 1
317
+ const flags = VFS.SQLITE_OPEN_MAIN_DB | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
318
+ const pOutFlags = new DataView(new ArrayBuffer(4))
319
+
320
+ vfs.jOpen(path, fileId, flags, pOutFlags)
321
+
322
+ const pageSize = 4096
323
+ const numPages = 10
324
+
325
+ // Write multiple SQLite pages
326
+ for (let pageNum = 0; pageNum < numPages; pageNum++) {
327
+ const pageData = new Uint8Array(pageSize)
328
+
329
+ // Fill page with pattern (page number repeated)
330
+ pageData.fill(pageNum % 256)
331
+
332
+ const offset = pageNum * pageSize
333
+ expect(vfs.jWrite(fileId, pageData, offset)).toBe(VFS.SQLITE_OK)
334
+ }
335
+
336
+ // Read back pages in different order
337
+ const readOrder = [3, 1, 7, 0, 9, 2, 5, 8, 4, 6]
338
+ for (const pageNum of readOrder) {
339
+ const readData = new Uint8Array(pageSize)
340
+ const offset = pageNum * pageSize
341
+
342
+ expect(vfs.jRead(fileId, readData, offset)).toBe(VFS.SQLITE_OK)
343
+
344
+ // Verify page content
345
+ const expectedValue = pageNum % 256
346
+ expect(readData.every((byte) => byte === expectedValue)).toBe(true)
347
+ }
348
+
349
+ expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
350
+ })
351
+
352
+ it('should handle WAL mode operations', async () => {
353
+ const mainPath = '/test/wal-mode.db'
354
+ const walPath = '/test/wal-mode.db-wal'
355
+
356
+ const mainFileId = 1
357
+ const walFileId = 2
358
+
359
+ const mainFlags = VFS.SQLITE_OPEN_MAIN_DB | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
360
+ const walFlags = VFS.SQLITE_OPEN_WAL | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
361
+ const pOutFlags = new DataView(new ArrayBuffer(4))
362
+
363
+ // Open main database file
364
+ expect(vfs.jOpen(mainPath, mainFileId, mainFlags, pOutFlags)).toBe(VFS.SQLITE_OK)
365
+
366
+ // Write main database content
367
+ const dbContent = new TextEncoder().encode('SQLite format 3\0Main database content')
368
+ expect(vfs.jWrite(mainFileId, dbContent, 0)).toBe(VFS.SQLITE_OK)
369
+
370
+ // Open WAL file
371
+ expect(vfs.jOpen(walPath, walFileId, walFlags, pOutFlags)).toBe(VFS.SQLITE_OK)
372
+
373
+ // Write WAL header and entries
374
+ const walHeader = new Uint8Array(32)
375
+ walHeader.fill(0x37) // WAL magic number pattern
376
+ expect(vfs.jWrite(walFileId, walHeader, 0)).toBe(VFS.SQLITE_OK)
377
+
378
+ // Write WAL frames
379
+ const frameSize = 4096 + 24 // Page size + frame header
380
+ const numFrames = 5
381
+
382
+ for (let frameNum = 0; frameNum < numFrames; frameNum++) {
383
+ const frameData = new Uint8Array(frameSize)
384
+ frameData.fill((frameNum + 1) % 256)
385
+
386
+ const offset = 32 + frameNum * frameSize // After WAL header
387
+ expect(vfs.jWrite(walFileId, frameData, offset)).toBe(VFS.SQLITE_OK)
388
+ }
389
+
390
+ // Read back WAL header
391
+ const readWalHeader = new Uint8Array(32)
392
+ expect(vfs.jRead(walFileId, readWalHeader, 0)).toBe(VFS.SQLITE_OK)
393
+ expect(readWalHeader).toEqual(walHeader)
394
+
395
+ // Read back WAL frames
396
+ for (let frameNum = 0; frameNum < numFrames; frameNum++) {
397
+ const readFrameData = new Uint8Array(frameSize)
398
+ const offset = 32 + frameNum * frameSize
399
+
400
+ expect(vfs.jRead(walFileId, readFrameData, offset)).toBe(VFS.SQLITE_OK)
401
+
402
+ const expectedValue = (frameNum + 1) % 256
403
+ expect(readFrameData.every((byte) => byte === expectedValue)).toBe(true)
404
+ }
405
+
406
+ // Close both files
407
+ expect(vfs.jClose(mainFileId)).toBe(VFS.SQLITE_OK)
408
+ expect(vfs.jClose(walFileId)).toBe(VFS.SQLITE_OK)
409
+ })
410
+
411
+ it('should handle journal mode operations', async () => {
412
+ const mainPath = '/test/journal-mode.db'
413
+ const journalPath = '/test/journal-mode.db-journal'
414
+
415
+ const mainFileId = 1
416
+ const journalFileId = 2
417
+
418
+ const mainFlags = VFS.SQLITE_OPEN_MAIN_DB | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
419
+ const journalFlags = VFS.SQLITE_OPEN_MAIN_JOURNAL | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
420
+ const pOutFlags = new DataView(new ArrayBuffer(4))
421
+
422
+ // Open main database
423
+ expect(vfs.jOpen(mainPath, mainFileId, mainFlags, pOutFlags)).toBe(VFS.SQLITE_OK)
424
+
425
+ // Write database pages
426
+ const pageSize = 4096
427
+ const dbPage = new Uint8Array(pageSize)
428
+ dbPage.fill(0xdb) // DB page pattern
429
+ expect(vfs.jWrite(mainFileId, dbPage, 0)).toBe(VFS.SQLITE_OK)
430
+
431
+ // Open journal file
432
+ expect(vfs.jOpen(journalPath, journalFileId, journalFlags, pOutFlags)).toBe(VFS.SQLITE_OK)
433
+
434
+ // Write journal header
435
+ const journalHeader = new TextEncoder().encode('Journal header\0\0\0\0')
436
+ expect(vfs.jWrite(journalFileId, journalHeader, 0)).toBe(VFS.SQLITE_OK)
437
+
438
+ // Write journal page (copy of original page for rollback)
439
+ const journalPage = new Uint8Array(pageSize)
440
+ journalPage.fill(0x4a) // Journal page pattern
441
+ expect(vfs.jWrite(journalFileId, journalPage, 512)).toBe(VFS.SQLITE_OK)
442
+
443
+ // Verify journal operations
444
+ const readJournalHeader = new Uint8Array(journalHeader.length)
445
+ expect(vfs.jRead(journalFileId, readJournalHeader, 0)).toBe(VFS.SQLITE_OK)
446
+ expect(readJournalHeader).toEqual(journalHeader)
447
+
448
+ const readJournalPage = new Uint8Array(pageSize)
449
+ expect(vfs.jRead(journalFileId, readJournalPage, 512)).toBe(VFS.SQLITE_OK)
450
+ expect(readJournalPage).toEqual(journalPage)
451
+
452
+ // Close files
453
+ expect(vfs.jClose(mainFileId)).toBe(VFS.SQLITE_OK)
454
+ expect(vfs.jClose(journalFileId)).toBe(VFS.SQLITE_OK)
455
+ })
456
+
457
+ it('should handle database file locking simulation', async () => {
458
+ const path = '/test/locking-simulation.db'
459
+ const fileId1 = 1
460
+ const fileId2 = 2
461
+
462
+ const flags = VFS.SQLITE_OPEN_MAIN_DB | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
463
+ const pOutFlags = new DataView(new ArrayBuffer(4))
464
+
465
+ // Open file with first handle
466
+ expect(vfs.jOpen(path, fileId1, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
467
+
468
+ // Write data with first handle
469
+ const testData1 = new TextEncoder().encode('First connection data')
470
+ expect(vfs.jWrite(fileId1, testData1, 0)).toBe(VFS.SQLITE_OK)
471
+
472
+ // Open same file with second handle (simulates multiple connections)
473
+ expect(vfs.jOpen(path, fileId2, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
474
+
475
+ // Read with second handle should see data from first handle
476
+ const readData = new Uint8Array(testData1.length)
477
+ expect(vfs.jRead(fileId2, readData, 0)).toBe(VFS.SQLITE_OK)
478
+ expect(readData).toEqual(testData1)
479
+
480
+ // Write with second handle
481
+ const testData2 = new TextEncoder().encode('Second connection data')
482
+ expect(vfs.jWrite(fileId2, testData2, 100)).toBe(VFS.SQLITE_OK)
483
+
484
+ // First handle should see data from second handle
485
+ const readData2 = new Uint8Array(testData2.length)
486
+ expect(vfs.jRead(fileId1, readData2, 100)).toBe(VFS.SQLITE_OK)
487
+ expect(readData2).toEqual(testData2)
488
+
489
+ // Close both handles
490
+ expect(vfs.jClose(fileId1)).toBe(VFS.SQLITE_OK)
491
+ expect(vfs.jClose(fileId2)).toBe(VFS.SQLITE_OK)
492
+ })
493
+ })
494
+
495
+ describe('End-to-End Integration', () => {
496
+ it('should handle complete SQLite workflow', async () => {
497
+ const dbPath = '/test/complete-workflow.db'
498
+ const walPath = '/test/complete-workflow.db-wal'
499
+ const journalPath = '/test/complete-workflow.db-journal'
500
+
501
+ const dbFileId = 1
502
+ const walFileId = 2
503
+ const journalFileId = 3
504
+
505
+ const pOutFlags = new DataView(new ArrayBuffer(4))
506
+
507
+ // 1. Create main database
508
+ const dbFlags = VFS.SQLITE_OPEN_MAIN_DB | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
509
+ expect(vfs.jOpen(dbPath, dbFileId, dbFlags, pOutFlags)).toBe(VFS.SQLITE_OK)
510
+
511
+ // Write SQLite header and initial pages
512
+ const sqliteHeader = new TextEncoder().encode('SQLite format 3\0')
513
+ expect(vfs.jWrite(dbFileId, sqliteHeader, 0)).toBe(VFS.SQLITE_OK)
514
+
515
+ const pageSize = 4096
516
+ const dbPage1 = new Uint8Array(pageSize)
517
+ dbPage1.fill(0x01)
518
+ expect(vfs.jWrite(dbFileId, dbPage1, pageSize)).toBe(VFS.SQLITE_OK)
519
+
520
+ // 2. Create journal for transaction
521
+ const journalFlags = VFS.SQLITE_OPEN_MAIN_JOURNAL | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
522
+ expect(vfs.jOpen(journalPath, journalFileId, journalFlags, pOutFlags)).toBe(VFS.SQLITE_OK)
523
+
524
+ // Write journal header and backup page
525
+ const journalHeader = new Uint8Array(512)
526
+ journalHeader.fill(0x30) // Journal header pattern
527
+ expect(vfs.jWrite(journalFileId, journalHeader, 0)).toBe(VFS.SQLITE_OK)
528
+
529
+ // 3. Modify database page (transaction)
530
+ const modifiedPage = new Uint8Array(pageSize)
531
+ modifiedPage.fill(0x02) // Modified page pattern
532
+ expect(vfs.jWrite(dbFileId, modifiedPage, pageSize)).toBe(VFS.SQLITE_OK)
533
+
534
+ // 4. Sync database (commit transaction)
535
+ expect(vfs.jSync(dbFileId, VFS.SQLITE_SYNC_NORMAL)).toBe(VFS.SQLITE_OK)
536
+
537
+ // 5. Delete journal (transaction committed)
538
+ expect(vfs.jClose(journalFileId)).toBe(VFS.SQLITE_OK)
539
+ expect(vfs.jDelete(journalPath, 0)).toBe(VFS.SQLITE_OK)
540
+
541
+ // 6. Switch to WAL mode
542
+ const walFlags = VFS.SQLITE_OPEN_WAL | VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
543
+ expect(vfs.jOpen(walPath, walFileId, walFlags, pOutFlags)).toBe(VFS.SQLITE_OK)
544
+
545
+ // Write WAL header
546
+ const walHeader = new Uint8Array(32)
547
+ walHeader.fill(0x37) // WAL header pattern
548
+ expect(vfs.jWrite(walFileId, walHeader, 0)).toBe(VFS.SQLITE_OK)
549
+
550
+ // Write WAL frame
551
+ const walFrame = new Uint8Array(pageSize + 24) // Page + frame header
552
+ walFrame.fill(0x03) // WAL frame pattern
553
+ expect(vfs.jWrite(walFileId, walFrame, 32)).toBe(VFS.SQLITE_OK)
554
+
555
+ // 7. Verify all files are accessible and contain expected data
556
+ // Database file
557
+ const readHeader = new Uint8Array(sqliteHeader.length)
558
+ expect(vfs.jRead(dbFileId, readHeader, 0)).toBe(VFS.SQLITE_OK)
559
+ expect(readHeader).toEqual(sqliteHeader)
560
+
561
+ const readDbPage = new Uint8Array(pageSize)
562
+ expect(vfs.jRead(dbFileId, readDbPage, pageSize)).toBe(VFS.SQLITE_OK)
563
+ expect(readDbPage).toEqual(modifiedPage)
564
+
565
+ // WAL file
566
+ const readWalHeader = new Uint8Array(32)
567
+ expect(vfs.jRead(walFileId, readWalHeader, 0)).toBe(VFS.SQLITE_OK)
568
+ expect(readWalHeader).toEqual(walHeader)
569
+
570
+ // 8. Close all files
571
+ expect(vfs.jClose(dbFileId)).toBe(VFS.SQLITE_OK)
572
+ expect(vfs.jClose(walFileId)).toBe(VFS.SQLITE_OK)
573
+
574
+ // 9. Verify persistence across VFS sessions
575
+ await new Promise((resolve) => setTimeout(resolve, 20))
576
+
577
+ expect(vfs.jOpen(dbPath, dbFileId, dbFlags, pOutFlags)).toBe(VFS.SQLITE_OK)
578
+ const persistentReadHeader = new Uint8Array(sqliteHeader.length)
579
+ expect(vfs.jRead(dbFileId, persistentReadHeader, 0)).toBe(VFS.SQLITE_OK)
580
+ expect(persistentReadHeader).toEqual(sqliteHeader)
581
+
582
+ expect(vfs.jClose(dbFileId)).toBe(VFS.SQLITE_OK)
583
+ })
584
+ })
585
+ })