@livestore/sqlite-wasm 0.0.0-snapshot-9507e455a5c1ff8ca4b9414bde007fe51bb2bcd0 → 0.0.0-snapshot-9a455c26eafff7fa10c95dadbab62f61092aceaa
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 +0 -4
- package/dist/FacadeVFS.js.map +1 -1
- package/dist/browser/mod.d.ts +7 -13
- package/dist/browser/mod.d.ts.map +1 -1
- package/dist/browser/mod.js +42 -47
- package/dist/browser/mod.js.map +1 -1
- package/dist/browser/opfs/AccessHandlePoolVFS.d.ts +4 -18
- package/dist/browser/opfs/AccessHandlePoolVFS.d.ts.map +1 -1
- package/dist/browser/opfs/AccessHandlePoolVFS.js +50 -44
- package/dist/browser/opfs/AccessHandlePoolVFS.js.map +1 -1
- package/dist/browser/opfs/index.d.ts +2 -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/package.json +5 -5
- package/src/FacadeVFS.ts +0 -5
- package/src/ambient.d.ts +18 -0
- package/src/browser/mod.ts +11 -33
- package/src/browser/opfs/AccessHandlePoolVFS.ts +52 -70
- package/src/browser/opfs/index.ts +3 -3
- package/src/browser/opfs/opfs-sah-pool.ts +1 -1
|
@@ -8,7 +8,7 @@ export const makeOpfsDb = ({ sqlite3, directory, fileName, }) => Effect.gen(func
|
|
|
8
8
|
const pathSegment = safePath.length === 0 ? '' : `-${safePath}`;
|
|
9
9
|
const vfsName = `opfs${pathSegment}`;
|
|
10
10
|
if (sqlite3.vfs_registered.has(vfsName) === false) {
|
|
11
|
-
const vfs = yield* AccessHandlePoolVFS.create(vfsName, directory, sqlite3.module);
|
|
11
|
+
const vfs = yield* Effect.promise(() => AccessHandlePoolVFS.create(vfsName, directory, sqlite3.module));
|
|
12
12
|
sqlite3.vfs_register(vfs, false);
|
|
13
13
|
opfsVfsMap.set(vfsName, vfs);
|
|
14
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/browser/opfs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/browser/opfs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAGhD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAE9D,MAAM,SAAS,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AAC9D,MAAM,UAAU,GAAG,IAAI,GAAG,EAA+B,CAAA;AAEzD,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EACzB,OAAO,EACP,SAAS,EACT,QAAQ,GAKT,EAAE,EAAE,CACH,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,kDAAkD;IAClD,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;IAC3D,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAA;IAC/D,MAAM,OAAO,GAAG,OAAO,WAAW,EAAE,CAAA;IAEpC,IAAI,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,EAAE,CAAC;QAClD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,EAAG,OAAe,CAAC,MAAM,CAAC,CAAC,CAAA;QAEhH,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAChC,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IAC9B,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;IACnE,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAE,CAAA;IAEpC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,CAAA;AAC3B,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opfs-sah-pool.d.ts","sourceRoot":"","sources":["../../../src/browser/opfs/opfs-sah-pool.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,kBAAkB,OAAc,CAAA;AAO7C,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"opfs-sah-pool.d.ts","sourceRoot":"","sources":["../../../src/browser/opfs/opfs-sah-pool.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,kBAAkB,OAAc,CAAA;AAO7C,eAAO,MAAM,qBAAqB,GAAU,MAAM,IAAI,KAAG,OAAO,CAAC,MAAM,CA+BtE,CAAA"}
|
|
@@ -9,7 +9,7 @@ const HEADER_OFFSET_DIGEST = HEADER_CORPUS_SIZE;
|
|
|
9
9
|
export const HEADER_OFFSET_DATA = SECTOR_SIZE;
|
|
10
10
|
const PERSISTENT_FILE_TYPES = VFS.SQLITE_OPEN_MAIN_DB | VFS.SQLITE_OPEN_MAIN_JOURNAL | VFS.SQLITE_OPEN_SUPER_JOURNAL | VFS.SQLITE_OPEN_WAL;
|
|
11
11
|
const textDecoder = new TextDecoder();
|
|
12
|
-
export const
|
|
12
|
+
export const decodeSAHPoolFilename = async (file) => {
|
|
13
13
|
// Read the path and digest of the path from the file.
|
|
14
14
|
const corpus = new Uint8Array(await file.slice(0, HEADER_CORPUS_SIZE).arrayBuffer());
|
|
15
15
|
// Delete files not expected to be present.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opfs-sah-pool.js","sourceRoot":"","sources":["../../../src/browser/opfs/opfs-sah-pool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,iCAAiC,CAAA;AAEtD,MAAM,WAAW,GAAG,IAAI,CAAA;AACxB,MAAM,oBAAoB,GAAG,GAAG,CAAA;AAChC,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAC3B,MAAM,kBAAkB,GAAG,CAAC,CAAA;AAC5B,MAAM,kBAAkB,GAAG,oBAAoB,GAAG,iBAAiB,CAAA;AACnE,MAAM,mBAAmB,GAAG,oBAAoB,CAAA;AAChD,MAAM,oBAAoB,GAAG,kBAAkB,CAAA;AAC/C,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAW,CAAA;AAE7C,MAAM,qBAAqB,GACzB,GAAG,CAAC,mBAAmB,GAAG,GAAG,CAAC,wBAAwB,GAAG,GAAG,CAAC,yBAAyB,GAAG,GAAG,CAAC,eAAe,CAAA;AAE9G,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAA;AAErC,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"opfs-sah-pool.js","sourceRoot":"","sources":["../../../src/browser/opfs/opfs-sah-pool.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livestore/sqlite-wasm",
|
|
3
|
-
"version": "0.0.0-snapshot-
|
|
3
|
+
"version": "0.0.0-snapshot-9a455c26eafff7fa10c95dadbab62f61092aceaa",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@cloudflare/workers-types": "4.20250923.0",
|
|
33
|
-
"@livestore/common": "0.0.0-snapshot-
|
|
34
|
-
"@livestore/common-cf": "0.0.0-snapshot-
|
|
35
|
-
"@livestore/utils": "0.0.0-snapshot-
|
|
36
|
-
"@livestore/wa-sqlite": "0.0.0-snapshot-
|
|
33
|
+
"@livestore/common": "0.0.0-snapshot-9a455c26eafff7fa10c95dadbab62f61092aceaa",
|
|
34
|
+
"@livestore/common-cf": "0.0.0-snapshot-9a455c26eafff7fa10c95dadbab62f61092aceaa",
|
|
35
|
+
"@livestore/utils": "0.0.0-snapshot-9a455c26eafff7fa10c95dadbab62f61092aceaa",
|
|
36
|
+
"@livestore/wa-sqlite": "0.0.0-snapshot-9a455c26eafff7fa10c95dadbab62f61092aceaa"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/chrome": "0.1.4",
|
package/src/FacadeVFS.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
// Based on https://github.com/rhashimoto/wa-sqlite/blob/master/src/FacadeVFS.js
|
|
2
|
-
//
|
|
3
|
-
// This TypeScript version lives in @livestore/sqlite-wasm instead of @livestore/wa-sqlite because:
|
|
4
|
-
// - @livestore/wa-sqlite is a direct wrapper of upstream wa-sqlite (kept as JavaScript for easier upstream sync)
|
|
5
|
-
// - @livestore/sqlite-wasm is where we add TypeScript abstractions on top of wa-sqlite
|
|
6
|
-
|
|
7
2
|
/** biome-ignore-all lint/complexity/useLiteralKeys: not needed for this file */
|
|
8
3
|
/** biome-ignore-all lint/correctness/noUnusedFunctionParameters: not needed for this file */
|
|
9
4
|
/** biome-ignore-all lint/complexity/noUselessConstructor: keep constructor for compatibility */
|
package/src/ambient.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle
|
|
2
|
+
interface FileSystemSyncAccessHandle {
|
|
3
|
+
close: () => void
|
|
4
|
+
flush: () => Promise<void>
|
|
5
|
+
getSize: () => number
|
|
6
|
+
read: (buffer: Uint8Array<ArrayBuffer> | Uint32Array, options?: FileSystemReadWriteOptions) => number
|
|
7
|
+
truncate: (newSize: number) => void
|
|
8
|
+
write: (buffer: Uint8Array<ArrayBuffer> | Uint32Array, options?: FileSystemReadWriteOptions) => number
|
|
9
|
+
seek: (offset: number) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface FileSystemReadWriteOptions {
|
|
13
|
+
at?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface FileSystemFileHandle {
|
|
17
|
+
createSyncAccessHandle: () => Promise<FileSystemSyncAccessHandle>
|
|
18
|
+
}
|
package/src/browser/mod.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MakeSqliteDb, PersistenceInfo, SqliteDb } from '@livestore/common'
|
|
2
|
-
import { Effect, Hash
|
|
2
|
+
import { Effect, Hash } from '@livestore/utils/effect'
|
|
3
3
|
import type { SQLiteAPI } from '@livestore/wa-sqlite'
|
|
4
4
|
import type { MemoryVFS } from '@livestore/wa-sqlite/src/examples/MemoryVFS.js'
|
|
5
5
|
|
|
@@ -10,11 +10,6 @@ import { makeOpfsDb } from './opfs/index.ts'
|
|
|
10
10
|
|
|
11
11
|
export * from './opfs/opfs-sah-pool.ts'
|
|
12
12
|
|
|
13
|
-
type WebDatabaseReq = {
|
|
14
|
-
dbPointer: number
|
|
15
|
-
persistenceInfo: PersistenceInfo
|
|
16
|
-
}
|
|
17
|
-
|
|
18
13
|
export type WebDatabaseMetadataInMemory = {
|
|
19
14
|
_tag: 'in-memory'
|
|
20
15
|
vfs: MemoryVFS
|
|
@@ -52,29 +47,18 @@ export type WebDatabaseInputOpfs = {
|
|
|
52
47
|
configureDb?: (db: SqliteDb) => void
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
type
|
|
50
|
+
export type WebDatabaseInput = WebDatabaseInputInMemory | WebDatabaseInputOpfs
|
|
56
51
|
|
|
57
|
-
type
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Opfs.Opfs | Scope.Scope
|
|
52
|
+
export type MakeWebSqliteDb = MakeSqliteDb<
|
|
53
|
+
{ dbPointer: number; persistenceInfo: PersistenceInfo },
|
|
54
|
+
WebDatabaseInput,
|
|
55
|
+
WebDatabaseMetadata
|
|
62
56
|
>
|
|
63
57
|
|
|
64
|
-
export
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
input: WebDatabaseInputInMemory | WebDatabaseInputOpfs,
|
|
69
|
-
): ReturnType<
|
|
70
|
-
MakeSqliteDb<
|
|
71
|
-
WebDatabaseReq,
|
|
72
|
-
WebDatabaseInputInMemory | WebDatabaseInputOpfs,
|
|
73
|
-
WebDatabaseMetadata,
|
|
74
|
-
Opfs.Opfs | Scope.Scope
|
|
75
|
-
>
|
|
76
|
-
> {
|
|
77
|
-
return Effect.gen(function* () {
|
|
58
|
+
export const sqliteDbFactory =
|
|
59
|
+
({ sqlite3 }: { sqlite3: SQLiteAPI }): MakeWebSqliteDb =>
|
|
60
|
+
(input: WebDatabaseInput) =>
|
|
61
|
+
Effect.gen(function* () {
|
|
78
62
|
if (input._tag === 'in-memory') {
|
|
79
63
|
const { dbPointer, vfs } = makeInMemoryDb(sqlite3)
|
|
80
64
|
return makeSqliteDb<WebDatabaseMetadataInMemory>({
|
|
@@ -89,7 +73,7 @@ export function sqliteDbFactory({ sqlite3 }: { sqlite3: SQLiteAPI }) {
|
|
|
89
73
|
fileName: ':memory:',
|
|
90
74
|
},
|
|
91
75
|
},
|
|
92
|
-
})
|
|
76
|
+
}) as any
|
|
93
77
|
}
|
|
94
78
|
|
|
95
79
|
// TODO figure out the actual max length
|
|
@@ -126,9 +110,3 @@ export function sqliteDbFactory({ sqlite3 }: { sqlite3: SQLiteAPI }) {
|
|
|
126
110
|
},
|
|
127
111
|
})
|
|
128
112
|
})
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return makeDb
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export type MakeWebSqliteDb = ReturnType<typeof sqliteDbFactory>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { Effect, Schedule, Schema } from '@livestore/utils/effect'
|
|
1
2
|
// Based on https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/AccessHandlePoolVFS.js
|
|
2
|
-
import { Effect, Opfs, Runtime, Schedule, Schema, type Scope, Stream } from '@livestore/utils/effect'
|
|
3
3
|
import * as VFS from '@livestore/wa-sqlite/src/VFS.js'
|
|
4
4
|
import { FacadeVFS } from '../../FacadeVFS.ts'
|
|
5
5
|
|
|
@@ -62,9 +62,6 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
62
62
|
#directoryPath
|
|
63
63
|
#directoryHandle: FileSystemDirectoryHandle | undefined
|
|
64
64
|
|
|
65
|
-
// Runtime for executing Effect operations
|
|
66
|
-
readonly #runtime: Runtime.Runtime<Opfs.Opfs | Scope.Scope>
|
|
67
|
-
|
|
68
65
|
// The OPFS files all have randomly-generated names that do not match
|
|
69
66
|
// the SQLite files whose data they contain. This map links those names
|
|
70
67
|
// with their respective OPFS access handles.
|
|
@@ -78,33 +75,17 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
78
75
|
|
|
79
76
|
#mapIdToFile = new Map<number, { path: string; flags: number; accessHandle: FileSystemSyncAccessHandle }>()
|
|
80
77
|
|
|
81
|
-
static create
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
yield* Effect.promise(() => vfs.isReady())
|
|
78
|
+
static async create(name: string, directoryPath: string, module: any) {
|
|
79
|
+
const vfs = new AccessHandlePoolVFS(name, directoryPath, module)
|
|
80
|
+
await vfs.isReady()
|
|
85
81
|
return vfs
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
constructor({
|
|
89
|
-
name,
|
|
90
|
-
directoryPath,
|
|
91
|
-
module,
|
|
92
|
-
runtime,
|
|
93
|
-
}: { name: string; directoryPath: string; module: any; runtime: Runtime.Runtime<Opfs.Opfs | Scope.Scope> }) {
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
constructor(name: string, directoryPath: string, module: any) {
|
|
94
85
|
super(name, module)
|
|
95
86
|
this.#directoryPath = directoryPath
|
|
96
|
-
this.#runtime = runtime
|
|
97
87
|
}
|
|
98
88
|
|
|
99
|
-
/**
|
|
100
|
-
* Get the OPFS file name that contains the data for the given SQLite file.
|
|
101
|
-
*
|
|
102
|
-
* @remarks
|
|
103
|
-
*
|
|
104
|
-
* This would be for one of the files in the pool managed by this VFS.
|
|
105
|
-
* It's not the same as the SQLite file name. It's a randomly-generated
|
|
106
|
-
* string that is not meaningful to the application.
|
|
107
|
-
*/
|
|
108
89
|
getOpfsFileName(zName: string) {
|
|
109
90
|
const path = this.#getPath(zName)
|
|
110
91
|
const accessHandle = this.#mapPathToAccessHandle.get(path)!
|
|
@@ -131,7 +112,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
131
112
|
})
|
|
132
113
|
}
|
|
133
114
|
|
|
134
|
-
const fileSize =
|
|
115
|
+
const fileSize = accessHandle.getSize()
|
|
135
116
|
if (fileSize <= HEADER_OFFSET_DATA) {
|
|
136
117
|
throw new OpfsError({
|
|
137
118
|
path,
|
|
@@ -143,9 +124,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
143
124
|
|
|
144
125
|
const payloadSize = fileSize - HEADER_OFFSET_DATA
|
|
145
126
|
const payload = new Uint8Array(payloadSize)
|
|
146
|
-
const bytesRead =
|
|
147
|
-
Runtime.runSync(this.#runtime),
|
|
148
|
-
)
|
|
127
|
+
const bytesRead = accessHandle.read(payload, { at: HEADER_OFFSET_DATA })
|
|
149
128
|
if (bytesRead !== payloadSize) {
|
|
150
129
|
throw new OpfsError({
|
|
151
130
|
path,
|
|
@@ -158,7 +137,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
158
137
|
resetAccessHandle(zName: string) {
|
|
159
138
|
const path = this.#getPath(zName)
|
|
160
139
|
const accessHandle = this.#mapPathToAccessHandle.get(path)!
|
|
161
|
-
|
|
140
|
+
accessHandle.truncate(HEADER_OFFSET_DATA)
|
|
162
141
|
// accessHandle.write(new Uint8Array(), { at: HEADER_OFFSET_DATA })
|
|
163
142
|
// accessHandle.flush()
|
|
164
143
|
}
|
|
@@ -199,7 +178,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
199
178
|
jClose(fileId: number): number {
|
|
200
179
|
const file = this.#mapIdToFile.get(fileId)
|
|
201
180
|
if (file) {
|
|
202
|
-
|
|
181
|
+
file.accessHandle.flush()
|
|
203
182
|
this.#mapIdToFile.delete(fileId)
|
|
204
183
|
if (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
|
|
205
184
|
this.#deletePath(file.path)
|
|
@@ -210,9 +189,9 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
210
189
|
|
|
211
190
|
jRead(fileId: number, pData: Uint8Array<ArrayBuffer>, iOffset: number): number {
|
|
212
191
|
const file = this.#mapIdToFile.get(fileId)!
|
|
213
|
-
const nBytes =
|
|
192
|
+
const nBytes = file.accessHandle.read(pData.subarray(), {
|
|
214
193
|
at: HEADER_OFFSET_DATA + iOffset,
|
|
215
|
-
})
|
|
194
|
+
})
|
|
216
195
|
if (nBytes < pData.byteLength) {
|
|
217
196
|
pData.fill(0, nBytes, pData.byteLength)
|
|
218
197
|
return VFS.SQLITE_IOERR_SHORT_READ
|
|
@@ -222,27 +201,27 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
222
201
|
|
|
223
202
|
jWrite(fileId: number, pData: Uint8Array<ArrayBuffer>, iOffset: number): number {
|
|
224
203
|
const file = this.#mapIdToFile.get(fileId)!
|
|
225
|
-
const nBytes =
|
|
204
|
+
const nBytes = file.accessHandle.write(pData.subarray(), {
|
|
226
205
|
at: HEADER_OFFSET_DATA + iOffset,
|
|
227
|
-
})
|
|
206
|
+
})
|
|
228
207
|
return nBytes === pData.byteLength ? VFS.SQLITE_OK : VFS.SQLITE_IOERR
|
|
229
208
|
}
|
|
230
209
|
|
|
231
210
|
jTruncate(fileId: number, iSize: number): number {
|
|
232
211
|
const file = this.#mapIdToFile.get(fileId)!
|
|
233
|
-
|
|
212
|
+
file.accessHandle.truncate(HEADER_OFFSET_DATA + iSize)
|
|
234
213
|
return VFS.SQLITE_OK
|
|
235
214
|
}
|
|
236
215
|
|
|
237
216
|
jSync(fileId: number, _flags: number): number {
|
|
238
217
|
const file = this.#mapIdToFile.get(fileId)!
|
|
239
|
-
|
|
218
|
+
file.accessHandle.flush()
|
|
240
219
|
return VFS.SQLITE_OK
|
|
241
220
|
}
|
|
242
221
|
|
|
243
222
|
jFileSize(fileId: number, pSize64: DataView): number {
|
|
244
223
|
const file = this.#mapIdToFile.get(fileId)!
|
|
245
|
-
const size =
|
|
224
|
+
const size = file.accessHandle.getSize() - HEADER_OFFSET_DATA
|
|
246
225
|
pSize64.setBigInt64(0, BigInt(size), true)
|
|
247
226
|
return VFS.SQLITE_OK
|
|
248
227
|
}
|
|
@@ -267,16 +246,20 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
267
246
|
return VFS.SQLITE_OK
|
|
268
247
|
}
|
|
269
248
|
|
|
270
|
-
close() {
|
|
249
|
+
async close() {
|
|
271
250
|
this.#releaseAccessHandles()
|
|
272
251
|
}
|
|
273
252
|
|
|
274
253
|
async isReady() {
|
|
275
254
|
if (!this.#directoryHandle) {
|
|
276
255
|
// All files are stored in a single directory.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
256
|
+
let handle = await navigator.storage.getDirectory()
|
|
257
|
+
for (const d of this.#directoryPath.split('/')) {
|
|
258
|
+
if (d) {
|
|
259
|
+
handle = await handle.getDirectoryHandle(d, { create: true })
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
this.#directoryHandle = handle
|
|
280
263
|
|
|
281
264
|
await this.#acquireAccessHandles()
|
|
282
265
|
if (this.getCapacity() === 0) {
|
|
@@ -316,12 +299,14 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
316
299
|
async addCapacity(n: number): Promise<number> {
|
|
317
300
|
for (let i = 0; i < n; ++i) {
|
|
318
301
|
const name = Math.random().toString(36).replace('0.', '')
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
302
|
+
const handle = await this.#directoryHandle!.getFileHandle(name, {
|
|
303
|
+
create: true,
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
const accessHandle = await Effect.tryPromise({
|
|
307
|
+
try: () => handle.createSyncAccessHandle(),
|
|
308
|
+
catch: (cause) => new OpfsError({ cause, path: name }),
|
|
309
|
+
}).pipe(Effect.retry(Schedule.exponentialBackoff10Sec), Effect.runPromise)
|
|
325
310
|
this.#mapAccessHandleToName.set(accessHandle, name)
|
|
326
311
|
|
|
327
312
|
this.#setAssociatedPath(accessHandle, '', 0)
|
|
@@ -341,7 +326,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
341
326
|
|
|
342
327
|
const name = this.#mapAccessHandleToName.get(accessHandle)!
|
|
343
328
|
accessHandle.close()
|
|
344
|
-
|
|
329
|
+
await this.#directoryHandle!.removeEntry(name)
|
|
345
330
|
this.#mapAccessHandleToName.delete(accessHandle)
|
|
346
331
|
this.#availableAccessHandles.delete(accessHandle)
|
|
347
332
|
++nRemoved
|
|
@@ -350,24 +335,21 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
350
335
|
}
|
|
351
336
|
|
|
352
337
|
async #acquireAccessHandles() {
|
|
353
|
-
const handlesStream = Opfs.Opfs.values(this.#directoryHandle!).pipe(Runtime.runSync(this.#runtime))
|
|
354
|
-
|
|
355
338
|
// Enumerate all the files in the directory.
|
|
356
|
-
const files =
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
339
|
+
const files = [] as [string, FileSystemFileHandle][]
|
|
340
|
+
for await (const [name, handle] of this.#directoryHandle!) {
|
|
341
|
+
if (handle.kind === 'file') {
|
|
342
|
+
files.push([name, handle as FileSystemFileHandle])
|
|
343
|
+
}
|
|
344
|
+
}
|
|
362
345
|
|
|
363
346
|
// Open access handles in parallel, separating associated and unassociated.
|
|
364
347
|
await Promise.all(
|
|
365
348
|
files.map(async ([name, handle]) => {
|
|
366
|
-
const accessHandle = await
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
)
|
|
349
|
+
const accessHandle = await Effect.tryPromise({
|
|
350
|
+
try: () => handle.createSyncAccessHandle(),
|
|
351
|
+
catch: (cause) => new OpfsError({ cause, path: name }),
|
|
352
|
+
}).pipe(Effect.retry(Schedule.exponentialBackoff10Sec), Effect.runPromise)
|
|
371
353
|
this.#mapAccessHandleToName.set(accessHandle, name)
|
|
372
354
|
const path = this.#getAssociatedPath(accessHandle)
|
|
373
355
|
if (path) {
|
|
@@ -396,7 +378,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
396
378
|
#getAssociatedPath(accessHandle: FileSystemSyncAccessHandle): string {
|
|
397
379
|
// Read the path and digest of the path from the file.
|
|
398
380
|
const corpus = new Uint8Array(HEADER_CORPUS_SIZE)
|
|
399
|
-
|
|
381
|
+
accessHandle.read(corpus, { at: 0 })
|
|
400
382
|
|
|
401
383
|
// Delete files not expected to be present.
|
|
402
384
|
const dataView = new DataView(corpus.buffer, corpus.byteOffset)
|
|
@@ -408,7 +390,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
408
390
|
}
|
|
409
391
|
|
|
410
392
|
const fileDigest = new Uint32Array(HEADER_DIGEST_SIZE / 4)
|
|
411
|
-
|
|
393
|
+
accessHandle.read(fileDigest, { at: HEADER_OFFSET_DIGEST })
|
|
412
394
|
|
|
413
395
|
// Verify the digest.
|
|
414
396
|
const computedDigest = this.#computeDigest(corpus)
|
|
@@ -420,7 +402,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
420
402
|
// truncated in #setAssociatedPath after the header is written. If
|
|
421
403
|
// an interruption occurs right before the truncation then garbage
|
|
422
404
|
// may remain in the file.
|
|
423
|
-
|
|
405
|
+
accessHandle.truncate(HEADER_OFFSET_DATA)
|
|
424
406
|
}
|
|
425
407
|
return new TextDecoder().decode(corpus.subarray(0, pathBytes))
|
|
426
408
|
} else {
|
|
@@ -448,9 +430,9 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
448
430
|
|
|
449
431
|
// Write the OPFS file header, including the digest.
|
|
450
432
|
const digest = this.#computeDigest(corpus)
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
433
|
+
accessHandle.write(corpus, { at: 0 })
|
|
434
|
+
accessHandle.write(digest, { at: HEADER_OFFSET_DIGEST })
|
|
435
|
+
accessHandle.flush()
|
|
454
436
|
|
|
455
437
|
if (path) {
|
|
456
438
|
this.#mapPathToAccessHandle.set(path, accessHandle)
|
|
@@ -458,7 +440,7 @@ export class AccessHandlePoolVFS extends FacadeVFS {
|
|
|
458
440
|
} else {
|
|
459
441
|
// This OPFS file doesn't represent any SQLite file so it doesn't
|
|
460
442
|
// need to keep any data.
|
|
461
|
-
|
|
443
|
+
accessHandle.truncate(HEADER_OFFSET_DATA)
|
|
462
444
|
this.#availableAccessHandles.add(accessHandle)
|
|
463
445
|
}
|
|
464
446
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect
|
|
1
|
+
import { Effect } from '@livestore/utils/effect'
|
|
2
2
|
import type * as WaSqlite from '@livestore/wa-sqlite'
|
|
3
3
|
|
|
4
4
|
import { AccessHandlePoolVFS } from './AccessHandlePoolVFS.ts'
|
|
@@ -14,7 +14,7 @@ export const makeOpfsDb = ({
|
|
|
14
14
|
sqlite3: WaSqlite.SQLiteAPI
|
|
15
15
|
directory: string
|
|
16
16
|
fileName: string
|
|
17
|
-
})
|
|
17
|
+
}) =>
|
|
18
18
|
Effect.gen(function* () {
|
|
19
19
|
// Replace all special characters with underscores
|
|
20
20
|
const safePath = directory.replaceAll(/["*/:<>?\\|]/g, '_')
|
|
@@ -22,7 +22,7 @@ export const makeOpfsDb = ({
|
|
|
22
22
|
const vfsName = `opfs${pathSegment}`
|
|
23
23
|
|
|
24
24
|
if (sqlite3.vfs_registered.has(vfsName) === false) {
|
|
25
|
-
const vfs = yield* AccessHandlePoolVFS.create(vfsName, directory, (sqlite3 as any).module)
|
|
25
|
+
const vfs = yield* Effect.promise(() => AccessHandlePoolVFS.create(vfsName, directory, (sqlite3 as any).module))
|
|
26
26
|
|
|
27
27
|
sqlite3.vfs_register(vfs, false)
|
|
28
28
|
opfsVfsMap.set(vfsName, vfs)
|
|
@@ -14,7 +14,7 @@ const PERSISTENT_FILE_TYPES =
|
|
|
14
14
|
|
|
15
15
|
const textDecoder = new TextDecoder()
|
|
16
16
|
|
|
17
|
-
export const
|
|
17
|
+
export const decodeSAHPoolFilename = async (file: File): Promise<string> => {
|
|
18
18
|
// Read the path and digest of the path from the file.
|
|
19
19
|
const corpus = new Uint8Array(await file.slice(0, HEADER_CORPUS_SIZE).arrayBuffer())
|
|
20
20
|
|