@tanstack/browser-db-sqlite-persistence 0.1.2
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/README.md +54 -0
- package/dist/assets/opfs-worker-CCciqEMo.js +5879 -0
- package/dist/assets/opfs-worker-CCciqEMo.js.map +1 -0
- package/dist/cjs/browser-coordinator.cjs +541 -0
- package/dist/cjs/browser-coordinator.cjs.map +1 -0
- package/dist/cjs/browser-coordinator.d.cts +67 -0
- package/dist/cjs/browser-persistence.cjs +83 -0
- package/dist/cjs/browser-persistence.cjs.map +1 -0
- package/dist/cjs/browser-persistence.d.cts +16 -0
- package/dist/cjs/index.cjs +14 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +8 -0
- package/dist/cjs/opfs-database.cjs +175 -0
- package/dist/cjs/opfs-database.cjs.map +1 -0
- package/dist/cjs/opfs-database.d.cts +10 -0
- package/dist/cjs/opfs-worker-protocol.d.cts +32 -0
- package/dist/cjs/opfs-worker.cjs +11 -0
- package/dist/cjs/opfs-worker.cjs.map +1 -0
- package/dist/cjs/opfs-worker.d.cts +1 -0
- package/dist/cjs/wa-sqlite-driver.cjs +106 -0
- package/dist/cjs/wa-sqlite-driver.cjs.map +1 -0
- package/dist/cjs/wa-sqlite-driver.d.cts +25 -0
- package/dist/esm/browser-coordinator.d.ts +67 -0
- package/dist/esm/browser-coordinator.js +541 -0
- package/dist/esm/browser-coordinator.js.map +1 -0
- package/dist/esm/browser-persistence.d.ts +16 -0
- package/dist/esm/browser-persistence.js +83 -0
- package/dist/esm/browser-persistence.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/opfs-database.d.ts +10 -0
- package/dist/esm/opfs-database.js +175 -0
- package/dist/esm/opfs-database.js.map +1 -0
- package/dist/esm/opfs-worker-protocol.d.ts +32 -0
- package/dist/esm/opfs-worker.d.ts +1 -0
- package/dist/esm/opfs-worker.js +12 -0
- package/dist/esm/opfs-worker.js.map +1 -0
- package/dist/esm/wa-sqlite-driver.d.ts +25 -0
- package/dist/esm/wa-sqlite-driver.js +106 -0
- package/dist/esm/wa-sqlite-driver.js.map +1 -0
- package/package.json +63 -0
- package/src/browser-coordinator.ts +842 -0
- package/src/browser-persistence.ts +172 -0
- package/src/index.ts +15 -0
- package/src/opfs-database.ts +258 -0
- package/src/opfs-worker-protocol.ts +47 -0
- package/src/opfs-worker.ts +305 -0
- package/src/vite-env.d.ts +1 -0
- package/src/wa-sqlite-driver.ts +153 -0
- package/src/wa-sqlite-examples.d.ts +10 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SingleProcessCoordinator,
|
|
3
|
+
createSQLiteCorePersistenceAdapter,
|
|
4
|
+
} from '@tanstack/db-sqlite-persistence-core'
|
|
5
|
+
import { BrowserCollectionCoordinator } from './browser-coordinator'
|
|
6
|
+
import { BrowserWASQLiteDriver } from './wa-sqlite-driver'
|
|
7
|
+
import type {
|
|
8
|
+
PersistedCollectionCoordinator,
|
|
9
|
+
PersistedCollectionMode,
|
|
10
|
+
PersistedCollectionPersistence,
|
|
11
|
+
SQLiteCoreAdapterOptions,
|
|
12
|
+
SQLiteDriver,
|
|
13
|
+
} from '@tanstack/db-sqlite-persistence-core'
|
|
14
|
+
import type { BrowserWASQLiteDatabase } from './wa-sqlite-driver'
|
|
15
|
+
|
|
16
|
+
export type { BrowserWASQLiteDatabase } from './wa-sqlite-driver'
|
|
17
|
+
|
|
18
|
+
type BrowserSQLiteCoreSchemaMismatchPolicy =
|
|
19
|
+
| `sync-present-reset`
|
|
20
|
+
| `sync-absent-error`
|
|
21
|
+
| `reset`
|
|
22
|
+
|
|
23
|
+
export type BrowserWASQLiteSchemaMismatchPolicy =
|
|
24
|
+
| BrowserSQLiteCoreSchemaMismatchPolicy
|
|
25
|
+
| `throw`
|
|
26
|
+
|
|
27
|
+
export type BrowserWASQLitePersistenceOptions = Omit<
|
|
28
|
+
SQLiteCoreAdapterOptions,
|
|
29
|
+
`driver` | `schemaVersion` | `schemaMismatchPolicy`
|
|
30
|
+
> & {
|
|
31
|
+
database: BrowserWASQLiteDatabase
|
|
32
|
+
coordinator?: PersistedCollectionCoordinator
|
|
33
|
+
schemaMismatchPolicy?: BrowserWASQLiteSchemaMismatchPolicy
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeSchemaMismatchPolicy(
|
|
37
|
+
policy: BrowserWASQLiteSchemaMismatchPolicy,
|
|
38
|
+
): BrowserSQLiteCoreSchemaMismatchPolicy {
|
|
39
|
+
if (policy === `throw`) {
|
|
40
|
+
return `sync-absent-error`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return policy
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveSchemaMismatchPolicy(
|
|
47
|
+
explicitPolicy: BrowserWASQLiteSchemaMismatchPolicy | undefined,
|
|
48
|
+
mode: PersistedCollectionMode,
|
|
49
|
+
): BrowserSQLiteCoreSchemaMismatchPolicy {
|
|
50
|
+
if (explicitPolicy) {
|
|
51
|
+
return normalizeSchemaMismatchPolicy(explicitPolicy)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createAdapterCacheKey(
|
|
58
|
+
schemaMismatchPolicy: BrowserSQLiteCoreSchemaMismatchPolicy,
|
|
59
|
+
schemaVersion: number | undefined,
|
|
60
|
+
): string {
|
|
61
|
+
const schemaVersionKey =
|
|
62
|
+
schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`
|
|
63
|
+
return `${schemaMismatchPolicy}|${schemaVersionKey}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createInternalSQLiteDriver(
|
|
67
|
+
options: BrowserWASQLitePersistenceOptions,
|
|
68
|
+
): SQLiteDriver {
|
|
69
|
+
return new BrowserWASQLiteDriver({
|
|
70
|
+
database: options.database,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolveAdapterBaseOptions(
|
|
75
|
+
options: BrowserWASQLitePersistenceOptions,
|
|
76
|
+
): Omit<
|
|
77
|
+
SQLiteCoreAdapterOptions,
|
|
78
|
+
`driver` | `schemaVersion` | `schemaMismatchPolicy`
|
|
79
|
+
> {
|
|
80
|
+
return {
|
|
81
|
+
appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,
|
|
82
|
+
appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,
|
|
83
|
+
pullSinceReloadThreshold: options.pullSinceReloadThreshold,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a shared browser wa-sqlite persistence instance that can be reused by
|
|
89
|
+
* many collections on the same database. This is single-tab wiring using
|
|
90
|
+
* SingleProcessCoordinator semantics (no election required).
|
|
91
|
+
*/
|
|
92
|
+
export function createBrowserWASQLitePersistence<
|
|
93
|
+
T extends object,
|
|
94
|
+
TKey extends string | number = string | number,
|
|
95
|
+
>(
|
|
96
|
+
options: BrowserWASQLitePersistenceOptions,
|
|
97
|
+
): PersistedCollectionPersistence<T, TKey> {
|
|
98
|
+
const { coordinator, schemaMismatchPolicy } = options
|
|
99
|
+
const driver = createInternalSQLiteDriver(options)
|
|
100
|
+
const adapterBaseOptions = resolveAdapterBaseOptions(options)
|
|
101
|
+
const resolvedCoordinator = coordinator ?? new SingleProcessCoordinator()
|
|
102
|
+
const adapterCache = new Map<
|
|
103
|
+
string,
|
|
104
|
+
ReturnType<
|
|
105
|
+
typeof createSQLiteCorePersistenceAdapter<
|
|
106
|
+
Record<string, unknown>,
|
|
107
|
+
string | number
|
|
108
|
+
>
|
|
109
|
+
>
|
|
110
|
+
>()
|
|
111
|
+
|
|
112
|
+
const getAdapterForCollection = (
|
|
113
|
+
mode: PersistedCollectionMode,
|
|
114
|
+
schemaVersion: number | undefined,
|
|
115
|
+
) => {
|
|
116
|
+
const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(
|
|
117
|
+
schemaMismatchPolicy,
|
|
118
|
+
mode,
|
|
119
|
+
)
|
|
120
|
+
const cacheKey = createAdapterCacheKey(
|
|
121
|
+
resolvedSchemaMismatchPolicy,
|
|
122
|
+
schemaVersion,
|
|
123
|
+
)
|
|
124
|
+
const cachedAdapter = adapterCache.get(cacheKey)
|
|
125
|
+
if (cachedAdapter) {
|
|
126
|
+
return cachedAdapter
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const adapter = createSQLiteCorePersistenceAdapter<
|
|
130
|
+
Record<string, unknown>,
|
|
131
|
+
string | number
|
|
132
|
+
>({
|
|
133
|
+
...adapterBaseOptions,
|
|
134
|
+
driver,
|
|
135
|
+
schemaMismatchPolicy: resolvedSchemaMismatchPolicy,
|
|
136
|
+
...(schemaVersion === undefined ? {} : { schemaVersion }),
|
|
137
|
+
})
|
|
138
|
+
adapterCache.set(cacheKey, adapter)
|
|
139
|
+
|
|
140
|
+
// Wire the adapter into the multi-tab coordinator so it can handle
|
|
141
|
+
// leader-side RPCs (applyCommittedTx, pullSince, ensureIndex, etc.)
|
|
142
|
+
if (resolvedCoordinator instanceof BrowserCollectionCoordinator) {
|
|
143
|
+
resolvedCoordinator.setAdapter(adapter)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return adapter
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const createCollectionPersistence = (
|
|
150
|
+
mode: PersistedCollectionMode,
|
|
151
|
+
schemaVersion: number | undefined,
|
|
152
|
+
): PersistedCollectionPersistence<T, TKey> => ({
|
|
153
|
+
adapter: getAdapterForCollection(
|
|
154
|
+
mode,
|
|
155
|
+
schemaVersion,
|
|
156
|
+
) as unknown as PersistedCollectionPersistence<T, TKey>[`adapter`],
|
|
157
|
+
coordinator: resolvedCoordinator,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const defaultPersistence = createCollectionPersistence(
|
|
161
|
+
`sync-absent`,
|
|
162
|
+
undefined,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
...defaultPersistence,
|
|
167
|
+
resolvePersistenceForCollection: ({ mode, schemaVersion }) =>
|
|
168
|
+
createCollectionPersistence(mode, schemaVersion),
|
|
169
|
+
resolvePersistenceForMode: (mode) =>
|
|
170
|
+
createCollectionPersistence(mode, undefined),
|
|
171
|
+
}
|
|
172
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { createBrowserWASQLitePersistence } from './browser-persistence'
|
|
2
|
+
export { openBrowserWASQLiteOPFSDatabase } from './opfs-database'
|
|
3
|
+
export { BrowserCollectionCoordinator } from './browser-coordinator'
|
|
4
|
+
export type {
|
|
5
|
+
BrowserWASQLiteDatabase,
|
|
6
|
+
BrowserWASQLitePersistenceOptions,
|
|
7
|
+
BrowserWASQLiteSchemaMismatchPolicy,
|
|
8
|
+
} from './browser-persistence'
|
|
9
|
+
export type { OpenBrowserWASQLiteOPFSDatabaseOptions } from './opfs-database'
|
|
10
|
+
export type { BrowserCollectionCoordinatorOptions } from './browser-coordinator'
|
|
11
|
+
export { persistedCollectionOptions } from '@tanstack/db-sqlite-persistence-core'
|
|
12
|
+
export type {
|
|
13
|
+
PersistedCollectionCoordinator,
|
|
14
|
+
PersistedCollectionPersistence,
|
|
15
|
+
} from '@tanstack/db-sqlite-persistence-core'
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InvalidPersistedCollectionConfigError,
|
|
3
|
+
PersistenceUnavailableError,
|
|
4
|
+
} from '@tanstack/db-sqlite-persistence-core'
|
|
5
|
+
import OPFSWorkerConstructor from './opfs-worker?worker'
|
|
6
|
+
import type { BrowserWASQLiteDatabase } from './wa-sqlite-driver'
|
|
7
|
+
import type {
|
|
8
|
+
BrowserOPFSWorkerErrorCode,
|
|
9
|
+
BrowserOPFSWorkerRequest,
|
|
10
|
+
BrowserOPFSWorkerResponse,
|
|
11
|
+
} from './opfs-worker-protocol'
|
|
12
|
+
|
|
13
|
+
const DEFAULT_VFS_NAME = `opfs`
|
|
14
|
+
type BrowserOPFSFeatureGlobal = {
|
|
15
|
+
navigator?: {
|
|
16
|
+
storage?: {
|
|
17
|
+
getDirectory?: () => Promise<unknown>
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
Worker?: typeof Worker
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type OpenBrowserWASQLiteOPFSDatabaseOptions = {
|
|
24
|
+
databaseName: string
|
|
25
|
+
vfsName?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type BrowserOPFSWorkerLike = Pick<
|
|
29
|
+
Worker,
|
|
30
|
+
`postMessage` | `terminate` | `addEventListener` | `removeEventListener`
|
|
31
|
+
>
|
|
32
|
+
|
|
33
|
+
type PendingWorkerRequest = {
|
|
34
|
+
resolve: (value: unknown) => void
|
|
35
|
+
reject: (error: Error) => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type BrowserOPFSWorkerRequestWithoutId =
|
|
39
|
+
BrowserOPFSWorkerRequest extends infer TRequest
|
|
40
|
+
? TRequest extends { requestId: string }
|
|
41
|
+
? Omit<TRequest, `requestId`>
|
|
42
|
+
: never
|
|
43
|
+
: never
|
|
44
|
+
|
|
45
|
+
class OPFSWorkerRequestError extends Error {
|
|
46
|
+
constructor(
|
|
47
|
+
readonly code: BrowserOPFSWorkerErrorCode,
|
|
48
|
+
message: string,
|
|
49
|
+
) {
|
|
50
|
+
super(message)
|
|
51
|
+
this.name = `OPFSWorkerRequestError`
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function hasOPFSBrowserPrerequisites(globalObject: unknown): boolean {
|
|
56
|
+
const candidate = globalObject as BrowserOPFSFeatureGlobal
|
|
57
|
+
const getDirectory = candidate.navigator?.storage?.getDirectory
|
|
58
|
+
const WorkerConstructor = candidate.Worker
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
typeof getDirectory === `function` &&
|
|
62
|
+
typeof WorkerConstructor === `function`
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createWorkerError(
|
|
67
|
+
code: BrowserOPFSWorkerErrorCode,
|
|
68
|
+
message: string,
|
|
69
|
+
): Error {
|
|
70
|
+
if (code === `PERSISTENCE_UNAVAILABLE`) {
|
|
71
|
+
return new PersistenceUnavailableError(message)
|
|
72
|
+
}
|
|
73
|
+
if (code === `INVALID_CONFIG`) {
|
|
74
|
+
return new InvalidPersistedCollectionConfigError(message)
|
|
75
|
+
}
|
|
76
|
+
return new OPFSWorkerRequestError(code, message)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createWorkerRequestIdFactory(): () => string {
|
|
80
|
+
let sequence = 0
|
|
81
|
+
const prefix = `opfs-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
sequence++
|
|
85
|
+
return `${prefix}-${sequence}`
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createOPFSWorkerInstance(): BrowserOPFSWorkerLike {
|
|
90
|
+
const WorkerConstructor = (
|
|
91
|
+
globalThis as typeof globalThis & { Worker?: typeof Worker }
|
|
92
|
+
).Worker
|
|
93
|
+
if (typeof WorkerConstructor !== `function`) {
|
|
94
|
+
throw new PersistenceUnavailableError(
|
|
95
|
+
`Web Worker support is required for browser OPFS persistence`,
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return new (OPFSWorkerConstructor as unknown as new () => BrowserOPFSWorkerLike)()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Creates a browser wa-sqlite database handle backed by OPFS and
|
|
104
|
+
* OPFSCoopSyncVFS.
|
|
105
|
+
*/
|
|
106
|
+
export async function openBrowserWASQLiteOPFSDatabase(
|
|
107
|
+
options: OpenBrowserWASQLiteOPFSDatabaseOptions,
|
|
108
|
+
): Promise<BrowserWASQLiteDatabase> {
|
|
109
|
+
const databaseName = options.databaseName.trim()
|
|
110
|
+
if (databaseName.length === 0) {
|
|
111
|
+
throw new InvalidPersistedCollectionConfigError(
|
|
112
|
+
`Browser wa-sqlite databaseName cannot be empty`,
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!hasOPFSBrowserPrerequisites(globalThis)) {
|
|
117
|
+
throw new PersistenceUnavailableError(
|
|
118
|
+
`Browser OPFS prerequisites are not available in this runtime`,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const vfsName = options.vfsName ?? DEFAULT_VFS_NAME
|
|
123
|
+
const worker = createOPFSWorkerInstance()
|
|
124
|
+
const nextRequestId = createWorkerRequestIdFactory()
|
|
125
|
+
const pendingRequests = new Map<string, PendingWorkerRequest>()
|
|
126
|
+
let disposed = false
|
|
127
|
+
|
|
128
|
+
const disposeWorker = (): void => {
|
|
129
|
+
if (disposed) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
disposed = true
|
|
133
|
+
worker.removeEventListener(`message`, onMessage)
|
|
134
|
+
worker.removeEventListener(`error`, onError)
|
|
135
|
+
worker.removeEventListener(`messageerror`, onMessageError)
|
|
136
|
+
worker.terminate()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const rejectAllPendingRequests = (error: Error): void => {
|
|
140
|
+
for (const pendingRequest of pendingRequests.values()) {
|
|
141
|
+
pendingRequest.reject(error)
|
|
142
|
+
}
|
|
143
|
+
pendingRequests.clear()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const onMessage = (event: Event): void => {
|
|
147
|
+
const messageEvent = event as MessageEvent<BrowserOPFSWorkerResponse>
|
|
148
|
+
const response = messageEvent.data
|
|
149
|
+
|
|
150
|
+
const pendingRequest = pendingRequests.get(response.requestId)
|
|
151
|
+
if (!pendingRequest) {
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
pendingRequests.delete(response.requestId)
|
|
155
|
+
|
|
156
|
+
if (response.ok) {
|
|
157
|
+
pendingRequest.resolve(response.rows ?? [])
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pendingRequest.reject(createWorkerError(response.code, response.error))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const onError = (): void => {
|
|
165
|
+
rejectAllPendingRequests(
|
|
166
|
+
new PersistenceUnavailableError(`OPFS worker terminated unexpectedly`),
|
|
167
|
+
)
|
|
168
|
+
disposeWorker()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const onMessageError = (): void => {
|
|
172
|
+
rejectAllPendingRequests(
|
|
173
|
+
new PersistenceUnavailableError(
|
|
174
|
+
`OPFS worker message serialization failed`,
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
disposeWorker()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
worker.addEventListener(`message`, onMessage)
|
|
181
|
+
worker.addEventListener(`error`, onError)
|
|
182
|
+
worker.addEventListener(`messageerror`, onMessageError)
|
|
183
|
+
|
|
184
|
+
const sendWorkerRequest = <T>(
|
|
185
|
+
request: BrowserOPFSWorkerRequestWithoutId,
|
|
186
|
+
): Promise<T> => {
|
|
187
|
+
if (disposed) {
|
|
188
|
+
return Promise.reject(
|
|
189
|
+
new InvalidPersistedCollectionConfigError(
|
|
190
|
+
`Browser OPFS worker connection is closed`,
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const requestId = nextRequestId()
|
|
196
|
+
return new Promise<T>((resolve, reject) => {
|
|
197
|
+
pendingRequests.set(requestId, {
|
|
198
|
+
resolve: (value) => {
|
|
199
|
+
resolve(value as T)
|
|
200
|
+
},
|
|
201
|
+
reject,
|
|
202
|
+
})
|
|
203
|
+
try {
|
|
204
|
+
const payload = {
|
|
205
|
+
...request,
|
|
206
|
+
requestId,
|
|
207
|
+
} as BrowserOPFSWorkerRequest
|
|
208
|
+
worker.postMessage(payload)
|
|
209
|
+
} catch (error) {
|
|
210
|
+
pendingRequests.delete(requestId)
|
|
211
|
+
reject(
|
|
212
|
+
new PersistenceUnavailableError(
|
|
213
|
+
`Failed to send message to OPFS worker: ${(error as Error).message}`,
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
await sendWorkerRequest<void>({
|
|
222
|
+
type: `init`,
|
|
223
|
+
databaseName,
|
|
224
|
+
vfsName,
|
|
225
|
+
})
|
|
226
|
+
} catch (error) {
|
|
227
|
+
disposeWorker()
|
|
228
|
+
throw error
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
execute: <TRow = unknown>(
|
|
233
|
+
sql: string,
|
|
234
|
+
params: ReadonlyArray<unknown> = [],
|
|
235
|
+
) =>
|
|
236
|
+
sendWorkerRequest<ReadonlyArray<TRow>>({
|
|
237
|
+
type: `execute`,
|
|
238
|
+
sql,
|
|
239
|
+
params,
|
|
240
|
+
}),
|
|
241
|
+
close: async () => {
|
|
242
|
+
let closeError: unknown
|
|
243
|
+
try {
|
|
244
|
+
await sendWorkerRequest<void>({
|
|
245
|
+
type: `close`,
|
|
246
|
+
})
|
|
247
|
+
} catch (error) {
|
|
248
|
+
closeError = error
|
|
249
|
+
} finally {
|
|
250
|
+
disposeWorker()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (closeError) {
|
|
254
|
+
throw closeError
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type BrowserOPFSWorkerErrorCode =
|
|
2
|
+
| `INVALID_CONFIG`
|
|
3
|
+
| `PERSISTENCE_UNAVAILABLE`
|
|
4
|
+
| `INTERNAL`
|
|
5
|
+
|
|
6
|
+
export type BrowserOPFSWorkerInitRequest = {
|
|
7
|
+
type: `init`
|
|
8
|
+
requestId: string
|
|
9
|
+
databaseName: string
|
|
10
|
+
vfsName: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type BrowserOPFSWorkerExecuteRequest = {
|
|
14
|
+
type: `execute`
|
|
15
|
+
requestId: string
|
|
16
|
+
sql: string
|
|
17
|
+
params: ReadonlyArray<unknown>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type BrowserOPFSWorkerCloseRequest = {
|
|
21
|
+
type: `close`
|
|
22
|
+
requestId: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type BrowserOPFSWorkerRequest =
|
|
26
|
+
| BrowserOPFSWorkerInitRequest
|
|
27
|
+
| BrowserOPFSWorkerExecuteRequest
|
|
28
|
+
| BrowserOPFSWorkerCloseRequest
|
|
29
|
+
|
|
30
|
+
export type BrowserOPFSWorkerSuccessResponse = {
|
|
31
|
+
type: `response`
|
|
32
|
+
requestId: string
|
|
33
|
+
ok: true
|
|
34
|
+
rows?: ReadonlyArray<Record<string, unknown>>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type BrowserOPFSWorkerErrorResponse = {
|
|
38
|
+
type: `response`
|
|
39
|
+
requestId: string
|
|
40
|
+
ok: false
|
|
41
|
+
code: BrowserOPFSWorkerErrorCode
|
|
42
|
+
error: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type BrowserOPFSWorkerResponse =
|
|
46
|
+
| BrowserOPFSWorkerSuccessResponse
|
|
47
|
+
| BrowserOPFSWorkerErrorResponse
|