@livestore/adapter-web 0.4.0-dev.21 → 0.4.0-dev.22
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/in-memory/in-memory-adapter.d.ts.map +1 -1
- package/dist/in-memory/in-memory-adapter.js +5 -1
- package/dist/in-memory/in-memory-adapter.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/single-tab/mod.d.ts +15 -0
- package/dist/single-tab/mod.d.ts.map +1 -0
- package/dist/single-tab/mod.js +15 -0
- package/dist/single-tab/mod.js.map +1 -0
- package/dist/single-tab/single-tab-adapter.d.ts +108 -0
- package/dist/single-tab/single-tab-adapter.d.ts.map +1 -0
- package/dist/single-tab/single-tab-adapter.js +279 -0
- package/dist/single-tab/single-tab-adapter.js.map +1 -0
- package/dist/web-worker/client-session/persisted-adapter.d.ts +18 -0
- package/dist/web-worker/client-session/persisted-adapter.d.ts.map +1 -1
- package/dist/web-worker/client-session/persisted-adapter.js +72 -5
- package/dist/web-worker/client-session/persisted-adapter.js.map +1 -1
- package/dist/web-worker/common/persisted-sqlite.js +1 -1
- package/dist/web-worker/common/persisted-sqlite.js.map +1 -1
- package/dist/web-worker/common/worker-schema.d.ts +8 -4
- package/dist/web-worker/common/worker-schema.d.ts.map +1 -1
- package/dist/web-worker/leader-worker/make-leader-worker.d.ts +1 -1
- package/dist/web-worker/leader-worker/make-leader-worker.d.ts.map +1 -1
- package/dist/web-worker/leader-worker/make-leader-worker.js +43 -9
- package/dist/web-worker/leader-worker/make-leader-worker.js.map +1 -1
- package/package.json +6 -6
- package/src/in-memory/in-memory-adapter.ts +5 -1
- package/src/index.ts +15 -1
- package/src/single-tab/mod.ts +15 -0
- package/src/single-tab/single-tab-adapter.ts +517 -0
- package/src/web-worker/client-session/persisted-adapter.ts +87 -6
- package/src/web-worker/common/persisted-sqlite.ts +1 -1
- package/src/web-worker/leader-worker/make-leader-worker.ts +59 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SqliteDb, SyncOptions } from '@livestore/common'
|
|
1
|
+
import type { BootStatus, BootWarningReason, SqliteDb, SyncOptions } from '@livestore/common'
|
|
2
2
|
import { Devtools, LogConfig, UnknownError } from '@livestore/common'
|
|
3
3
|
import type { DevtoolsOptions, StreamEventsOptions } from '@livestore/common/leader-thread'
|
|
4
4
|
import {
|
|
@@ -22,12 +22,12 @@ import {
|
|
|
22
22
|
Layer,
|
|
23
23
|
OtelTracer,
|
|
24
24
|
Scheduler,
|
|
25
|
-
|
|
25
|
+
Schema,
|
|
26
26
|
Stream,
|
|
27
27
|
TaskTracing,
|
|
28
28
|
WorkerRunner,
|
|
29
29
|
} from '@livestore/utils/effect'
|
|
30
|
-
import { BrowserWorkerRunner, Opfs } from '@livestore/utils/effect/browser'
|
|
30
|
+
import { BrowserWorkerRunner, Opfs, WebError } from '@livestore/utils/effect/browser'
|
|
31
31
|
import type * as otel from '@opentelemetry/api'
|
|
32
32
|
|
|
33
33
|
import { cleanupOldStateDbFiles, getStateDbFileName, sanitizeOpfsDir } from '../common/persisted-sqlite.ts'
|
|
@@ -118,13 +118,28 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }:
|
|
|
118
118
|
Effect.gen(function* () {
|
|
119
119
|
const sqlite3 = yield* Effect.promise(() => loadSqlite3Wasm())
|
|
120
120
|
const makeSqliteDb = sqliteDbFactory({ sqlite3 })
|
|
121
|
-
const opfsDirectory = yield* sanitizeOpfsDir(storageOptions.directory, storeId)
|
|
122
121
|
const runtime = yield* Effect.runtime<never>()
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
// Check OPFS availability and determine storage mode
|
|
124
|
+
const opfsCheck = yield* checkOpfsAvailability
|
|
125
|
+
const useOpfs = opfsCheck === undefined
|
|
126
|
+
|
|
127
|
+
// Track boot warning to emit later
|
|
128
|
+
let bootWarning: BootStatus | undefined
|
|
129
|
+
if (!useOpfs) {
|
|
130
|
+
yield* Effect.logWarning(
|
|
131
|
+
'[@livestore/adapter-web:worker] OPFS unavailable, using in-memory storage',
|
|
132
|
+
opfsCheck,
|
|
133
|
+
)
|
|
134
|
+
bootWarning = { stage: 'warning', ...opfsCheck }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const opfsDirectory = useOpfs ? yield* sanitizeOpfsDir(storageOptions.directory, storeId) : undefined
|
|
138
|
+
|
|
139
|
+
const makeOpfsDb = (kind: 'state' | 'eventlog') =>
|
|
125
140
|
makeSqliteDb({
|
|
126
141
|
_tag: 'opfs',
|
|
127
|
-
opfsDirectory
|
|
142
|
+
opfsDirectory: opfsDirectory!,
|
|
128
143
|
fileName: kind === 'state' ? getStateDbFileName(schema) : 'eventlog.db',
|
|
129
144
|
configureDb: (db) =>
|
|
130
145
|
configureConnection(db, {
|
|
@@ -137,10 +152,17 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }:
|
|
|
137
152
|
}).pipe(Effect.provide(runtime), Effect.runSync),
|
|
138
153
|
}).pipe(Effect.acquireRelease((db) => Effect.try(() => db.close()).pipe(Effect.ignoreLogged)))
|
|
139
154
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
155
|
+
const makeInMemoryDb = () =>
|
|
156
|
+
makeSqliteDb({
|
|
157
|
+
_tag: 'in-memory',
|
|
158
|
+
configureDb: (db) =>
|
|
159
|
+
configureConnection(db, { foreignKeys: true }).pipe(Effect.provide(runtime), Effect.runSync),
|
|
160
|
+
}).pipe(Effect.acquireRelease((db) => Effect.try(() => db.close()).pipe(Effect.ignoreLogged)))
|
|
161
|
+
|
|
162
|
+
// Use OPFS if available, otherwise fall back to in-memory
|
|
163
|
+
const [dbState, dbEventlog] = useOpfs
|
|
164
|
+
? yield* Effect.all([makeOpfsDb('state'), makeOpfsDb('eventlog')], { concurrency: 2 })
|
|
165
|
+
: yield* Effect.all([makeInMemoryDb(), makeInMemoryDb()], { concurrency: 2 })
|
|
144
166
|
|
|
145
167
|
// Clean up old state database files after successful database creation
|
|
146
168
|
// This prevents OPFS file pool capacity exhaustion from accumulated state db files after schema changes/migrations
|
|
@@ -167,6 +189,7 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }:
|
|
|
167
189
|
shutdownChannel,
|
|
168
190
|
syncPayloadEncoded,
|
|
169
191
|
syncPayloadSchema,
|
|
192
|
+
...(bootWarning !== undefined ? { bootWarning } : {}),
|
|
170
193
|
})
|
|
171
194
|
}).pipe(
|
|
172
195
|
Effect.tapCauseLogPretty,
|
|
@@ -301,3 +324,29 @@ const makeDevtoolsOptions = ({
|
|
|
301
324
|
}),
|
|
302
325
|
}
|
|
303
326
|
})
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Attempts to access OPFS and returns a warning if unavailable.
|
|
330
|
+
*
|
|
331
|
+
* Common failure scenarios:
|
|
332
|
+
* - Safari/Firefox private browsing: SecurityError or NotAllowedError
|
|
333
|
+
* - Permission denied: NotAllowedError
|
|
334
|
+
* - Quota exceeded: QuotaExceededError
|
|
335
|
+
*/
|
|
336
|
+
const checkOpfsAvailability = Effect.gen(function* () {
|
|
337
|
+
const opfs = yield* Opfs.Opfs
|
|
338
|
+
return yield* opfs.getRootDirectoryHandle.pipe(
|
|
339
|
+
Effect.as(undefined),
|
|
340
|
+
Effect.catchAll((error) => {
|
|
341
|
+
const reason: BootWarningReason =
|
|
342
|
+
Schema.is(WebError.SecurityError)(error) || Schema.is(WebError.NotAllowedError)(error)
|
|
343
|
+
? 'private-browsing'
|
|
344
|
+
: 'storage-unavailable'
|
|
345
|
+
const message =
|
|
346
|
+
reason === 'private-browsing'
|
|
347
|
+
? 'Storage unavailable in private browsing mode. LiveStore will continue without persistence.'
|
|
348
|
+
: 'Storage access denied. LiveStore will continue without persistence.'
|
|
349
|
+
return Effect.succeed({ reason, message } as const)
|
|
350
|
+
}),
|
|
351
|
+
)
|
|
352
|
+
})
|