@livestore/sqlite-wasm 0.4.0-dev.2 → 0.4.0-dev.20
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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/FacadeVFS.d.ts.map +1 -1
- package/dist/FacadeVFS.js +4 -0
- package/dist/FacadeVFS.js.map +1 -1
- package/dist/browser/mod.d.ts +15 -7
- package/dist/browser/mod.d.ts.map +1 -1
- package/dist/browser/mod.js +49 -43
- package/dist/browser/mod.js.map +1 -1
- package/dist/browser/opfs/AccessHandlePoolVFS.d.ts +40 -17
- package/dist/browser/opfs/AccessHandlePoolVFS.d.ts.map +1 -1
- package/dist/browser/opfs/AccessHandlePoolVFS.js +211 -143
- package/dist/browser/opfs/AccessHandlePoolVFS.js.map +1 -1
- package/dist/browser/opfs/index.d.ts +3 -2
- package/dist/browser/opfs/index.d.ts.map +1 -1
- package/dist/browser/opfs/index.js +1 -1
- package/dist/browser/opfs/index.js.map +1 -1
- package/dist/browser/opfs/opfs-sah-pool.d.ts +1 -1
- package/dist/browser/opfs/opfs-sah-pool.d.ts.map +1 -1
- package/dist/browser/opfs/opfs-sah-pool.js +1 -1
- package/dist/browser/opfs/opfs-sah-pool.js.map +1 -1
- package/dist/cf/BlockManager.d.ts +61 -0
- package/dist/cf/BlockManager.d.ts.map +1 -0
- package/dist/cf/BlockManager.js +157 -0
- package/dist/cf/BlockManager.js.map +1 -0
- package/dist/cf/CloudflareSqlVFS.d.ts +51 -0
- package/dist/cf/CloudflareSqlVFS.d.ts.map +1 -0
- package/dist/cf/CloudflareSqlVFS.js +351 -0
- package/dist/cf/CloudflareSqlVFS.js.map +1 -0
- package/dist/cf/CloudflareWorkerVFS.d.ts +72 -0
- package/dist/cf/CloudflareWorkerVFS.d.ts.map +1 -0
- package/dist/cf/CloudflareWorkerVFS.js +552 -0
- package/dist/cf/CloudflareWorkerVFS.js.map +1 -0
- package/dist/cf/mod.d.ts +43 -0
- package/dist/cf/mod.d.ts.map +1 -0
- package/dist/cf/mod.js +74 -0
- package/dist/cf/mod.js.map +1 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.d.ts +2 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.d.ts.map +1 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.js +314 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.js.map +1 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.d.ts +2 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.d.ts.map +1 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.js +266 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-core.test.js.map +1 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.d.ts +2 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.d.ts.map +1 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.js +462 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-integration.test.js.map +1 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.d.ts +2 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.d.ts.map +1 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.js +334 -0
- package/dist/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.js.map +1 -0
- package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.d.ts +2 -0
- package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.d.ts.map +1 -0
- package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.js +354 -0
- package/dist/cf/test/sql/cloudflare-sql-vfs-core.test.js.map +1 -0
- package/dist/load-wasm/mod.node.d.ts.map +1 -1
- package/dist/load-wasm/mod.node.js +1 -2
- package/dist/load-wasm/mod.node.js.map +1 -1
- package/dist/load-wasm/mod.workerd.d.ts +2 -0
- package/dist/load-wasm/mod.workerd.d.ts.map +1 -0
- package/dist/load-wasm/mod.workerd.js +28 -0
- package/dist/load-wasm/mod.workerd.js.map +1 -0
- package/dist/make-sqlite-db.d.ts +1 -0
- package/dist/make-sqlite-db.d.ts.map +1 -1
- package/dist/make-sqlite-db.js +29 -8
- package/dist/make-sqlite-db.js.map +1 -1
- package/dist/node/NodeFS.d.ts +1 -2
- package/dist/node/NodeFS.d.ts.map +1 -1
- package/dist/node/NodeFS.js +1 -6
- package/dist/node/NodeFS.js.map +1 -1
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +5 -10
- package/dist/node/mod.js.map +1 -1
- package/package.json +21 -8
- package/src/FacadeVFS.ts +5 -0
- package/src/browser/mod.ts +39 -13
- package/src/browser/opfs/AccessHandlePoolVFS.ts +387 -225
- package/src/browser/opfs/index.ts +4 -3
- package/src/browser/opfs/opfs-sah-pool.ts +1 -1
- package/src/cf/BlockManager.ts +225 -0
- package/src/cf/CloudflareSqlVFS.ts +450 -0
- package/src/cf/CloudflareWorkerVFS.ts +664 -0
- package/src/cf/README.md +60 -0
- package/src/cf/mod.ts +143 -0
- package/src/cf/test/README.md +224 -0
- package/src/cf/test/async-storage/cloudflare-worker-vfs-advanced.test.ts +389 -0
- package/src/cf/test/async-storage/cloudflare-worker-vfs-core.test.ts +322 -0
- package/src/cf/test/async-storage/cloudflare-worker-vfs-integration.test.ts +585 -0
- package/src/cf/test/async-storage/cloudflare-worker-vfs-reliability.test.ts +403 -0
- package/src/cf/test/sql/cloudflare-sql-vfs-core.test.ts +433 -0
- package/src/load-wasm/mod.node.ts +1 -2
- package/src/load-wasm/mod.workerd.ts +28 -0
- package/src/make-sqlite-db.ts +39 -8
- package/src/node/NodeFS.ts +1 -9
- package/src/node/mod.ts +5 -12
- package/src/ambient.d.ts +0 -18
|
@@ -0,0 +1,403 @@
|
|
|
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 - Reliability & Error Recovery', () => {
|
|
9
|
+
let vfs: CloudflareWorkerVFS
|
|
10
|
+
let mockStorage: CfTypes.DurableObjectStorage
|
|
11
|
+
let storageData: Map<string, any>
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
storageData = new Map<string, any>()
|
|
15
|
+
|
|
16
|
+
mockStorage = {
|
|
17
|
+
get: (async (_key: string | string[]) => {
|
|
18
|
+
if (Array.isArray(_key)) {
|
|
19
|
+
return new Map()
|
|
20
|
+
}
|
|
21
|
+
return storageData.get(_key)
|
|
22
|
+
}) as CfTypes.DurableObjectStorage['get'],
|
|
23
|
+
|
|
24
|
+
put: async (_key: string | Record<string, any>, _value?: any) => {
|
|
25
|
+
if (typeof _key === 'string') {
|
|
26
|
+
storageData.set(_key, _value)
|
|
27
|
+
} else {
|
|
28
|
+
for (const [k, v] of Object.entries(_key)) {
|
|
29
|
+
storageData.set(k, v)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
delete: (async (_key: string | string[]) => {
|
|
35
|
+
if (Array.isArray(_key)) {
|
|
36
|
+
let count = 0
|
|
37
|
+
for (const k of _key) {
|
|
38
|
+
if (storageData.delete(k)) count++
|
|
39
|
+
}
|
|
40
|
+
return count
|
|
41
|
+
} else {
|
|
42
|
+
return storageData.delete(_key)
|
|
43
|
+
}
|
|
44
|
+
}) as CfTypes.DurableObjectStorage['delete'],
|
|
45
|
+
|
|
46
|
+
list: async () => new Map(storageData),
|
|
47
|
+
sync: async () => {},
|
|
48
|
+
transactionSync: (fn: () => any) => fn(),
|
|
49
|
+
deleteAll: async () => {
|
|
50
|
+
storageData.clear()
|
|
51
|
+
},
|
|
52
|
+
transaction: async (fn: (txn: any) => Promise<any>) => fn({} as any),
|
|
53
|
+
getCurrentBookmark: async () => '',
|
|
54
|
+
getBookmarkForTime: async (_time: number | Date) => '',
|
|
55
|
+
onNextSessionRestoreBookmark: async (_bookmark: string) => '',
|
|
56
|
+
getAlarm: async () => null,
|
|
57
|
+
setAlarm: async (_timestamp: number | Date) => {},
|
|
58
|
+
deleteAlarm: async () => {},
|
|
59
|
+
sql: {} as any,
|
|
60
|
+
} as CfTypes.DurableObjectStorage
|
|
61
|
+
|
|
62
|
+
vfs = new CloudflareWorkerVFS('test-reliability-vfs', mockStorage, {})
|
|
63
|
+
await vfs.isReady()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('Error Recovery', () => {
|
|
67
|
+
it('should handle storage failures gracefully during reads', async () => {
|
|
68
|
+
const path = '/test/read-failure-recovery.db'
|
|
69
|
+
const fileId = 1
|
|
70
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
71
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
72
|
+
|
|
73
|
+
vfs.jOpen(path, fileId, flags, pOutFlags)
|
|
74
|
+
|
|
75
|
+
// Write data successfully first
|
|
76
|
+
const testData = new TextEncoder().encode('Test data for failure recovery')
|
|
77
|
+
expect(vfs.jWrite(fileId, testData, 0)).toBe(VFS.SQLITE_OK)
|
|
78
|
+
|
|
79
|
+
// Read should work initially (from cache)
|
|
80
|
+
const readData1 = new Uint8Array(testData.length)
|
|
81
|
+
expect(vfs.jRead(fileId, readData1, 0)).toBe(VFS.SQLITE_OK)
|
|
82
|
+
expect(readData1).toEqual(testData)
|
|
83
|
+
|
|
84
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should handle invalid file operations gracefully', async () => {
|
|
88
|
+
const invalidFileId = 999
|
|
89
|
+
const buffer = new Uint8Array(100)
|
|
90
|
+
|
|
91
|
+
// All operations on invalid file ID should return appropriate error
|
|
92
|
+
expect(vfs.jRead(invalidFileId, buffer, 0)).toBe(VFS.SQLITE_IOERR)
|
|
93
|
+
expect(vfs.jWrite(invalidFileId, buffer, 0)).toBe(VFS.SQLITE_IOERR)
|
|
94
|
+
expect(vfs.jTruncate(invalidFileId, 50)).toBe(VFS.SQLITE_IOERR)
|
|
95
|
+
expect(vfs.jSync(invalidFileId, VFS.SQLITE_SYNC_NORMAL)).toBe(VFS.SQLITE_IOERR)
|
|
96
|
+
expect(vfs.jClose(invalidFileId)).toBe(VFS.SQLITE_OK)
|
|
97
|
+
|
|
98
|
+
const pSize64 = new DataView(new ArrayBuffer(8))
|
|
99
|
+
expect(vfs.jFileSize(invalidFileId, pSize64)).toBe(VFS.SQLITE_IOERR)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('should handle operations on closed files gracefully', async () => {
|
|
103
|
+
const path = '/test/closed-file-ops.db'
|
|
104
|
+
const fileId = 1
|
|
105
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
106
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
107
|
+
|
|
108
|
+
// Open and immediately close file
|
|
109
|
+
vfs.jOpen(path, fileId, flags, pOutFlags)
|
|
110
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
111
|
+
|
|
112
|
+
// Operations on closed file should fail gracefully
|
|
113
|
+
const buffer = new Uint8Array(10)
|
|
114
|
+
expect(vfs.jRead(fileId, buffer, 0)).toBe(VFS.SQLITE_IOERR)
|
|
115
|
+
expect(vfs.jWrite(fileId, buffer, 0)).toBe(VFS.SQLITE_IOERR)
|
|
116
|
+
expect(vfs.jTruncate(fileId, 5)).toBe(VFS.SQLITE_IOERR)
|
|
117
|
+
expect(vfs.jSync(fileId, VFS.SQLITE_SYNC_NORMAL)).toBe(VFS.SQLITE_IOERR)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should handle invalid paths gracefully', async () => {
|
|
121
|
+
const invalidPaths = ['', null as any, undefined as any]
|
|
122
|
+
const fileId = 1
|
|
123
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
124
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
125
|
+
|
|
126
|
+
for (const invalidPath of invalidPaths) {
|
|
127
|
+
const result = vfs.jOpen(invalidPath, fileId, flags, pOutFlags)
|
|
128
|
+
expect(result).toBe(VFS.SQLITE_OK)
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should recover from corrupted metadata gracefully', async () => {
|
|
133
|
+
const path = '/test/corrupted-metadata.db'
|
|
134
|
+
const fileId = 1
|
|
135
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
136
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
137
|
+
|
|
138
|
+
// Manually insert corrupted metadata
|
|
139
|
+
const metadataKey = `file:${path}:meta`
|
|
140
|
+
storageData.set(metadataKey, { invalid: 'metadata', structure: true })
|
|
141
|
+
|
|
142
|
+
// Opening file should handle corrupted metadata
|
|
143
|
+
expect(vfs.jOpen(path, fileId, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
|
|
144
|
+
|
|
145
|
+
// Should be able to write new data (which will create new metadata)
|
|
146
|
+
const testData = new TextEncoder().encode('Recovery test data')
|
|
147
|
+
expect(vfs.jWrite(fileId, testData, 0)).toBe(VFS.SQLITE_OK)
|
|
148
|
+
|
|
149
|
+
// Should be able to read the data back
|
|
150
|
+
const readData = new Uint8Array(testData.length)
|
|
151
|
+
expect(vfs.jRead(fileId, readData, 0)).toBe(VFS.SQLITE_OK)
|
|
152
|
+
expect(readData).toEqual(testData)
|
|
153
|
+
|
|
154
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
describe('Concurrent Operations', () => {
|
|
159
|
+
it('should handle multiple files opened simultaneously', async () => {
|
|
160
|
+
const numFiles = 10
|
|
161
|
+
const files: Array<{ path: string; id: number }> = []
|
|
162
|
+
|
|
163
|
+
// Open multiple files
|
|
164
|
+
for (let i = 0; i < numFiles; i++) {
|
|
165
|
+
const path = `/test/concurrent-${i}.db`
|
|
166
|
+
const fileId = i + 1
|
|
167
|
+
files.push({ path, id: fileId })
|
|
168
|
+
|
|
169
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
170
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
171
|
+
expect(vfs.jOpen(path, fileId, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Write different data to each file
|
|
175
|
+
for (let i = 0; i < files.length; i++) {
|
|
176
|
+
const testData = new TextEncoder().encode(`File ${i} data`)
|
|
177
|
+
expect(vfs.jWrite(files[i]?.id ?? 0, testData, 0)).toBe(VFS.SQLITE_OK)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Read back and verify each file has correct data
|
|
181
|
+
for (let i = 0; i < files.length; i++) {
|
|
182
|
+
const expectedData = new TextEncoder().encode(`File ${i} data`)
|
|
183
|
+
const readData = new Uint8Array(expectedData.length)
|
|
184
|
+
expect(vfs.jRead(files[i]?.id ?? 0, readData, 0)).toBe(VFS.SQLITE_OK)
|
|
185
|
+
expect(readData).toEqual(expectedData)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Close all files
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
expect(vfs.jClose(file.id)).toBe(VFS.SQLITE_OK)
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should handle rapid sequential operations on same file', async () => {
|
|
195
|
+
const path = '/test/rapid-sequential.db'
|
|
196
|
+
const fileId = 1
|
|
197
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
198
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
199
|
+
|
|
200
|
+
vfs.jOpen(path, fileId, flags, pOutFlags)
|
|
201
|
+
|
|
202
|
+
// Perform rapid sequential write operations
|
|
203
|
+
const numOperations = 50
|
|
204
|
+
for (let i = 0; i < numOperations; i++) {
|
|
205
|
+
const data = new TextEncoder().encode(`Operation ${i}`)
|
|
206
|
+
const offset = i * 20 // Non-overlapping writes
|
|
207
|
+
expect(vfs.jWrite(fileId, data, offset)).toBe(VFS.SQLITE_OK)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Verify all operations succeeded
|
|
211
|
+
for (let i = 0; i < numOperations; i++) {
|
|
212
|
+
const expectedData = new TextEncoder().encode(`Operation ${i}`)
|
|
213
|
+
const readData = new Uint8Array(expectedData.length)
|
|
214
|
+
const offset = i * 20
|
|
215
|
+
expect(vfs.jRead(fileId, readData, offset)).toBe(VFS.SQLITE_OK)
|
|
216
|
+
expect(readData).toEqual(expectedData)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should handle mixed read/write operations', async () => {
|
|
223
|
+
const path = '/test/mixed-operations.db'
|
|
224
|
+
const fileId = 1
|
|
225
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
226
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
227
|
+
|
|
228
|
+
vfs.jOpen(path, fileId, flags, pOutFlags)
|
|
229
|
+
|
|
230
|
+
// Initialize with some data
|
|
231
|
+
const initialData = new Uint8Array(1000)
|
|
232
|
+
for (let i = 0; i < initialData.length; i++) {
|
|
233
|
+
initialData[i] = i % 256
|
|
234
|
+
}
|
|
235
|
+
expect(vfs.jWrite(fileId, initialData, 0)).toBe(VFS.SQLITE_OK)
|
|
236
|
+
|
|
237
|
+
// Perform mixed operations
|
|
238
|
+
for (let i = 0; i < 20; i++) {
|
|
239
|
+
if (i % 2 === 0) {
|
|
240
|
+
// Write operation
|
|
241
|
+
const writeData = new Uint8Array(10)
|
|
242
|
+
writeData.fill((i + 100) % 256)
|
|
243
|
+
const offset = (i * 10) % 500
|
|
244
|
+
expect(vfs.jWrite(fileId, writeData, offset)).toBe(VFS.SQLITE_OK)
|
|
245
|
+
} else {
|
|
246
|
+
// Read operation
|
|
247
|
+
const readData = new Uint8Array(10)
|
|
248
|
+
const offset = ((i - 1) * 10) % 500
|
|
249
|
+
expect(vfs.jRead(fileId, readData, offset)).toBe(VFS.SQLITE_OK)
|
|
250
|
+
// Verify read data matches what we wrote in previous iteration
|
|
251
|
+
const expectedValue = (i - 1 + 100) % 256
|
|
252
|
+
expect(readData.every((byte) => byte === expectedValue)).toBe(true)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should handle cache pressure under concurrent access', async () => {
|
|
260
|
+
const path = '/test/cache-pressure.db'
|
|
261
|
+
const fileId = 1
|
|
262
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
263
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
264
|
+
|
|
265
|
+
vfs.jOpen(path, fileId, flags, pOutFlags)
|
|
266
|
+
|
|
267
|
+
const chunkSize = 64 * 1024
|
|
268
|
+
const numChunks = 15 // More than typical cache size
|
|
269
|
+
|
|
270
|
+
// Write data to create cache pressure
|
|
271
|
+
for (let i = 0; i < numChunks; i++) {
|
|
272
|
+
const chunkData = new Uint8Array(chunkSize)
|
|
273
|
+
chunkData.fill(i % 256)
|
|
274
|
+
const offset = i * chunkSize
|
|
275
|
+
expect(vfs.jWrite(fileId, chunkData, offset)).toBe(VFS.SQLITE_OK)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Random access pattern to stress cache
|
|
279
|
+
const accessPattern = []
|
|
280
|
+
for (let i = 0; i < 30; i++) {
|
|
281
|
+
accessPattern.push(Math.floor(Math.random() * numChunks))
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (const chunkIdx of accessPattern) {
|
|
285
|
+
const readData = new Uint8Array(1000) // Read partial chunk
|
|
286
|
+
const offset = chunkIdx * chunkSize + 1000
|
|
287
|
+
const readResult = vfs.jRead(fileId, readData, offset)
|
|
288
|
+
|
|
289
|
+
if (readResult === VFS.SQLITE_OK) {
|
|
290
|
+
const expectedValue = chunkIdx % 256
|
|
291
|
+
expect(readData.every((byte) => byte === expectedValue)).toBe(true)
|
|
292
|
+
} else {
|
|
293
|
+
// Cache miss is acceptable under cache pressure
|
|
294
|
+
expect(readResult).toBe(VFS.SQLITE_IOERR_SHORT_READ)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
describe('Resource Management', () => {
|
|
303
|
+
it('should handle resource cleanup properly', async () => {
|
|
304
|
+
const paths = ['/test/cleanup1.db', '/test/cleanup2.db', '/test/cleanup3.db']
|
|
305
|
+
const fileIds = [1, 2, 3]
|
|
306
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
307
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
308
|
+
|
|
309
|
+
// Open files and write data
|
|
310
|
+
for (let i = 0; i < paths.length; i++) {
|
|
311
|
+
expect(vfs.jOpen(paths[i] ?? '', fileIds[i] ?? 0, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
|
|
312
|
+
|
|
313
|
+
const testData = new TextEncoder().encode(`Cleanup test ${i}`)
|
|
314
|
+
expect(vfs.jWrite(fileIds[i] ?? 0, testData, 0)).toBe(VFS.SQLITE_OK)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Get initial stats
|
|
318
|
+
const statsBefore = vfs.getStats()
|
|
319
|
+
expect(statsBefore.openFiles).toBe(3)
|
|
320
|
+
|
|
321
|
+
// Close all files
|
|
322
|
+
for (const fileId of fileIds) {
|
|
323
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Verify cleanup
|
|
327
|
+
const statsAfter = vfs.getStats()
|
|
328
|
+
expect(statsAfter.openFiles).toBe(0)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('should handle file deletion and cleanup', async () => {
|
|
332
|
+
const path = '/test/delete-cleanup.db'
|
|
333
|
+
const fileId = 1
|
|
334
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
335
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
336
|
+
|
|
337
|
+
// Create and write to file
|
|
338
|
+
vfs.jOpen(path, fileId, flags, pOutFlags)
|
|
339
|
+
const testData = new TextEncoder().encode('Data to be deleted')
|
|
340
|
+
expect(vfs.jWrite(fileId, testData, 0)).toBe(VFS.SQLITE_OK)
|
|
341
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
342
|
+
|
|
343
|
+
// Wait for async operations
|
|
344
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
345
|
+
|
|
346
|
+
// Verify file exists
|
|
347
|
+
const pResOut = new DataView(new ArrayBuffer(4))
|
|
348
|
+
expect(vfs.jAccess(path, VFS.SQLITE_ACCESS_EXISTS, pResOut)).toBe(VFS.SQLITE_OK)
|
|
349
|
+
|
|
350
|
+
// Delete file
|
|
351
|
+
expect(vfs.jDelete(path, 0)).toBe(VFS.SQLITE_OK)
|
|
352
|
+
|
|
353
|
+
// Wait for cleanup
|
|
354
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
355
|
+
|
|
356
|
+
// Verify file is deleted
|
|
357
|
+
vfs.jAccess(path, VFS.SQLITE_ACCESS_EXISTS, pResOut)
|
|
358
|
+
expect(pResOut.getUint32(0, true)).toBe(0)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('should handle memory pressure gracefully', async () => {
|
|
362
|
+
// Create multiple files with large data to simulate memory pressure
|
|
363
|
+
const numFiles = 5
|
|
364
|
+
const chunkSize = 64 * 1024
|
|
365
|
+
const dataPerFile = chunkSize * 2 // 128KB per file
|
|
366
|
+
|
|
367
|
+
for (let fileIdx = 0; fileIdx < numFiles; fileIdx++) {
|
|
368
|
+
const path = `/test/memory-pressure-${fileIdx}.db`
|
|
369
|
+
const fileId = fileIdx + 1
|
|
370
|
+
const flags = VFS.SQLITE_OPEN_CREATE | VFS.SQLITE_OPEN_READWRITE
|
|
371
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
372
|
+
|
|
373
|
+
expect(vfs.jOpen(path, fileId, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
|
|
374
|
+
|
|
375
|
+
// Write large data
|
|
376
|
+
const largeData = new Uint8Array(dataPerFile)
|
|
377
|
+
largeData.fill(fileIdx % 256)
|
|
378
|
+
expect(vfs.jWrite(fileId, largeData, 0)).toBe(VFS.SQLITE_OK)
|
|
379
|
+
|
|
380
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Verify all files are still accessible
|
|
384
|
+
for (let fileIdx = 0; fileIdx < numFiles; fileIdx++) {
|
|
385
|
+
const path = `/test/memory-pressure-${fileIdx}.db`
|
|
386
|
+
const fileId = fileIdx + 1
|
|
387
|
+
const flags = VFS.SQLITE_OPEN_READWRITE
|
|
388
|
+
const pOutFlags = new DataView(new ArrayBuffer(4))
|
|
389
|
+
|
|
390
|
+
expect(vfs.jOpen(path, fileId, flags, pOutFlags)).toBe(VFS.SQLITE_OK)
|
|
391
|
+
|
|
392
|
+
// Read and verify data
|
|
393
|
+
const readData = new Uint8Array(dataPerFile)
|
|
394
|
+
expect(vfs.jRead(fileId, readData, 0)).toBe(VFS.SQLITE_OK)
|
|
395
|
+
|
|
396
|
+
const expectedValue = fileIdx % 256
|
|
397
|
+
expect(readData.every((byte) => byte === expectedValue)).toBe(true)
|
|
398
|
+
|
|
399
|
+
expect(vfs.jClose(fileId)).toBe(VFS.SQLITE_OK)
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
})
|
|
403
|
+
})
|