@livestore/adapter-web 0.4.0-dev.8 → 0.4.0

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 (74) hide show
  1. package/README.md +5 -5
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/in-memory/in-memory-adapter.d.ts +49 -5
  4. package/dist/in-memory/in-memory-adapter.d.ts.map +1 -1
  5. package/dist/in-memory/in-memory-adapter.js +77 -20
  6. package/dist/in-memory/in-memory-adapter.js.map +1 -1
  7. package/dist/index.d.ts +11 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +11 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/single-tab/mod.d.ts +15 -0
  12. package/dist/single-tab/mod.d.ts.map +1 -0
  13. package/dist/single-tab/mod.js +15 -0
  14. package/dist/single-tab/mod.js.map +1 -0
  15. package/dist/single-tab/single-tab-adapter.d.ts +108 -0
  16. package/dist/single-tab/single-tab-adapter.d.ts.map +1 -0
  17. package/dist/single-tab/single-tab-adapter.js +271 -0
  18. package/dist/single-tab/single-tab-adapter.js.map +1 -0
  19. package/dist/web-worker/client-session/client-session-devtools.d.ts +2 -2
  20. package/dist/web-worker/client-session/client-session-devtools.d.ts.map +1 -1
  21. package/dist/web-worker/client-session/client-session-devtools.js +20 -9
  22. package/dist/web-worker/client-session/client-session-devtools.js.map +1 -1
  23. package/dist/web-worker/client-session/persisted-adapter.d.ts +18 -0
  24. package/dist/web-worker/client-session/persisted-adapter.d.ts.map +1 -1
  25. package/dist/web-worker/client-session/persisted-adapter.js +141 -67
  26. package/dist/web-worker/client-session/persisted-adapter.js.map +1 -1
  27. package/dist/web-worker/client-session/sqlite-loader.d.ts +2 -0
  28. package/dist/web-worker/client-session/sqlite-loader.d.ts.map +1 -0
  29. package/dist/web-worker/client-session/sqlite-loader.js +16 -0
  30. package/dist/web-worker/client-session/sqlite-loader.js.map +1 -0
  31. package/dist/web-worker/common/persisted-sqlite.d.ts +13 -20
  32. package/dist/web-worker/common/persisted-sqlite.d.ts.map +1 -1
  33. package/dist/web-worker/common/persisted-sqlite.js +95 -102
  34. package/dist/web-worker/common/persisted-sqlite.js.map +1 -1
  35. package/dist/web-worker/common/shutdown-channel.d.ts +3 -2
  36. package/dist/web-worker/common/shutdown-channel.d.ts.map +1 -1
  37. package/dist/web-worker/common/shutdown-channel.js +2 -2
  38. package/dist/web-worker/common/shutdown-channel.js.map +1 -1
  39. package/dist/web-worker/common/worker-disconnect-channel.d.ts +2 -6
  40. package/dist/web-worker/common/worker-disconnect-channel.d.ts.map +1 -1
  41. package/dist/web-worker/common/worker-disconnect-channel.js +3 -2
  42. package/dist/web-worker/common/worker-disconnect-channel.js.map +1 -1
  43. package/dist/web-worker/common/worker-schema.d.ts +152 -58
  44. package/dist/web-worker/common/worker-schema.d.ts.map +1 -1
  45. package/dist/web-worker/common/worker-schema.js +55 -37
  46. package/dist/web-worker/common/worker-schema.js.map +1 -1
  47. package/dist/web-worker/leader-worker/make-leader-worker.d.ts +5 -3
  48. package/dist/web-worker/leader-worker/make-leader-worker.d.ts.map +1 -1
  49. package/dist/web-worker/leader-worker/make-leader-worker.js +99 -38
  50. package/dist/web-worker/leader-worker/make-leader-worker.js.map +1 -1
  51. package/dist/web-worker/shared-worker/make-shared-worker.d.ts +2 -1
  52. package/dist/web-worker/shared-worker/make-shared-worker.d.ts.map +1 -1
  53. package/dist/web-worker/shared-worker/make-shared-worker.js +62 -52
  54. package/dist/web-worker/shared-worker/make-shared-worker.js.map +1 -1
  55. package/package.json +56 -18
  56. package/src/in-memory/in-memory-adapter.ts +92 -26
  57. package/src/index.ts +15 -1
  58. package/src/single-tab/mod.ts +15 -0
  59. package/src/single-tab/single-tab-adapter.ts +499 -0
  60. package/src/web-worker/ambient.d.ts +7 -24
  61. package/src/web-worker/client-session/client-session-devtools.ts +32 -18
  62. package/src/web-worker/client-session/persisted-adapter.ts +199 -103
  63. package/src/web-worker/client-session/sqlite-loader.ts +19 -0
  64. package/src/web-worker/common/persisted-sqlite.ts +215 -170
  65. package/src/web-worker/common/shutdown-channel.ts +10 -3
  66. package/src/web-worker/common/worker-disconnect-channel.ts +10 -3
  67. package/src/web-worker/common/worker-schema.ts +78 -38
  68. package/src/web-worker/leader-worker/make-leader-worker.ts +149 -71
  69. package/src/web-worker/shared-worker/make-shared-worker.ts +78 -90
  70. package/dist/opfs-utils.d.ts +0 -5
  71. package/dist/opfs-utils.d.ts.map +0 -1
  72. package/dist/opfs-utils.js +0 -43
  73. package/dist/opfs-utils.js.map +0 -1
  74. package/src/opfs-utils.ts +0 -61
@@ -1,19 +1,15 @@
1
- import { Devtools, UnexpectedError } from '@livestore/common'
1
+ import { Devtools, isWorkerTransportError, LogConfig, liveStoreVersion, UnknownError } from '@livestore/common'
2
2
  import * as DevtoolsWeb from '@livestore/devtools-web-common/web-channel'
3
3
  import * as WebmeshWorker from '@livestore/devtools-web-common/worker'
4
4
  import { isDevEnv, isNotUndefined, LS_DEV } from '@livestore/utils'
5
5
  import {
6
- BrowserWorker,
7
- BrowserWorkerRunner,
8
6
  Deferred,
9
7
  Effect,
10
8
  Exit,
11
9
  FetchHttpClient,
12
10
  identity,
13
11
  Layer,
14
- Logger,
15
- LogLevel,
16
- ParseResult,
12
+ Option,
17
13
  Ref,
18
14
  Schema,
19
15
  Scope,
@@ -21,9 +17,9 @@ import {
21
17
  SubscriptionRef,
22
18
  TaskTracing,
23
19
  Worker,
24
- WorkerError,
25
20
  WorkerRunner,
26
21
  } from '@livestore/utils/effect'
22
+ import { BrowserWorker, BrowserWorkerRunner } from '@livestore/utils/effect/browser'
27
23
 
28
24
  import { makeShutdownChannel } from '../common/shutdown-channel.ts'
29
25
  import * as WorkerSchema from '../common/worker-schema.ts'
@@ -42,15 +38,16 @@ navigator.locks.request(
42
38
  async () => new Promise(() => {}),
43
39
  )
44
40
 
45
- if (isDevEnv()) {
41
+ if (isDevEnv() === true) {
46
42
  globalThis.__debugLiveStoreUtils = {
47
43
  blobUrl: (buffer: Uint8Array<ArrayBuffer>) =>
48
44
  URL.createObjectURL(new Blob([buffer], { type: 'application/octet-stream' })),
49
- runSync: (effect: Effect.Effect<any, any, never>) => Effect.runSync(effect),
50
- runFork: (effect: Effect.Effect<any, any, never>) => Effect.runFork(effect),
45
+ runSync: <A, E>(effect: Effect.Effect<A, E>) => Effect.runSync(effect),
46
+ runFork: <A, E>(effect: Effect.Effect<A, E>) => Effect.runFork(effect),
51
47
  }
52
48
  }
53
49
 
50
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- `SerializedRunner.Handlers` uses `any` in the R channel, propagating as `unknown` in `HandlersContext`
54
51
  const makeWorkerRunner = Effect.gen(function* () {
55
52
  const leaderWorkerContextSubRef = yield* SubscriptionRef.make<
56
53
  | {
@@ -60,22 +57,18 @@ const makeWorkerRunner = Effect.gen(function* () {
60
57
  | undefined
61
58
  >(undefined)
62
59
 
63
- const initialMessagePayloadDeferredRef = yield* Deferred.make<
64
- typeof WorkerSchema.SharedWorkerInitialMessagePayloadFromClientSession.Type
65
- >().pipe(Effect.andThen(Ref.make))
66
-
67
60
  const waitForWorker = SubscriptionRef.waitUntil(leaderWorkerContextSubRef, isNotUndefined).pipe(
68
61
  Effect.map((_) => _.worker),
69
62
  )
70
63
 
71
- const forwardRequest = <TReq extends WorkerSchema.LeaderWorkerInnerRequest>(
72
- req: TReq,
73
- ): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer _R>
74
- ? Effect.Effect<A, UnexpectedError, never>
75
- : never =>
64
+ const forwardRequest = <A, I, E, EI, R>(
65
+ req: WorkerSchema.LeaderWorkerInnerRequest & Schema.WithResult<A, I, E, EI, R>,
66
+ ): Effect.Effect<A, E, R> =>
67
+ // Forward the request to the active worker and convert transport errors to defects.
76
68
  waitForWorker.pipe(
77
69
  // Effect.logBefore(`forwardRequest: ${req._tag}`),
78
- Effect.andThen((worker) => worker.executeEffect(req) as Effect.Effect<unknown, unknown, never>),
70
+ Effect.andThen((worker) => worker.executeEffect(req)),
71
+ Effect.catchIf(isWorkerTransportError, (e) => Effect.die(e)),
79
72
  // Effect.tap((_) => Effect.log(`forwardRequest: ${req._tag}`, _)),
80
73
  // Effect.tapError((cause) => Effect.logError(`forwardRequest err: ${req._tag}`, cause)),
81
74
  Effect.interruptible,
@@ -83,27 +76,18 @@ const makeWorkerRunner = Effect.gen(function* () {
83
76
  label: `@livestore/adapter-web:shared-worker:forwardRequest:${req._tag}`,
84
77
  duration: 500,
85
78
  }),
86
- Effect.mapError((cause) =>
87
- Schema.is(UnexpectedError)(cause)
88
- ? cause
89
- : ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
90
- ? new UnexpectedError({ cause })
91
- : cause,
92
- ),
93
- Effect.catchAllDefect((cause) => new UnexpectedError({ cause })),
94
79
  Effect.tapCauseLogPretty,
95
- ) as any
80
+ )
96
81
 
97
- const forwardRequestStream = <TReq extends WorkerSchema.LeaderWorkerInnerRequest>(
98
- req: TReq,
99
- ): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer _R>
100
- ? Stream.Stream<A, UnexpectedError, never>
101
- : never =>
82
+ const forwardRequestStream = <A, I, E, EI, R>(
83
+ req: WorkerSchema.LeaderWorkerInnerRequest & Schema.WithResult<A, I, E, EI, R>,
84
+ ): Stream.Stream<A, E, R> =>
102
85
  Effect.gen(function* () {
103
86
  yield* Effect.logDebug(`forwardRequestStream: ${req._tag}`)
104
87
  const { worker, scope } = yield* SubscriptionRef.waitUntil(leaderWorkerContextSubRef, isNotUndefined)
105
- const stream = worker.execute(req) as Stream.Stream<unknown, unknown, never>
106
-
88
+ const stream = worker.execute(req).pipe(
89
+ Stream.refineOrDie((e) => isWorkerTransportError(e) === true ? Option.none() : Option.some(e)),
90
+ )
107
91
  // It seems the request stream is not automatically interrupted when the scope shuts down
108
92
  // so we need to manually interrupt it when the scope shuts down
109
93
  const shutdownDeferred = yield* Deferred.make<void>()
@@ -118,12 +102,10 @@ const makeWorkerRunner = Effect.gen(function* () {
118
102
  return Stream.merge(stream, scopeShutdownStream, { haltStrategy: 'either' })
119
103
  }).pipe(
120
104
  Effect.interruptible,
121
- UnexpectedError.mapToUnexpectedError,
122
105
  Effect.tapCauseLogPretty,
123
106
  Stream.unwrap,
124
107
  Stream.ensuring(Effect.logDebug(`shutting down stream for ${req._tag}`)),
125
- UnexpectedError.mapToUnexpectedErrorStream,
126
- ) as any
108
+ )
127
109
 
128
110
  const resetCurrentWorkerCtx = Effect.gen(function* () {
129
111
  const prevWorker = yield* SubscriptionRef.get(leaderWorkerContextSubRef)
@@ -142,62 +124,60 @@ const makeWorkerRunner = Effect.gen(function* () {
142
124
  }
143
125
  }).pipe(Effect.withSpan('@livestore/adapter-web:shared-worker:resetCurrentWorkerCtx'))
144
126
 
145
- // const devtoolsWebBridge = yield* makeDevtoolsWebBridge
146
-
147
127
  const reset = Effect.gen(function* () {
148
128
  yield* Effect.logDebug('reset')
149
-
150
- const initialMessagePayloadDeferred =
151
- yield* Deferred.make<typeof WorkerSchema.SharedWorkerInitialMessagePayloadFromClientSession.Type>()
152
- yield* Ref.set(initialMessagePayloadDeferredRef, initialMessagePayloadDeferred)
153
-
129
+ // Clear cached invariants so a fresh configuration can be accepted after shutdown
130
+ yield* Ref.set(invariantsRef, undefined)
131
+ // Tear down current leader worker context
154
132
  yield* resetCurrentWorkerCtx
155
- // yield* devtoolsWebBridge.reset
156
133
  })
157
134
 
158
- return WorkerRunner.layerSerialized(WorkerSchema.SharedWorkerRequest, {
159
- InitialMessage: (message) =>
160
- Effect.gen(function* () {
161
- if (message.payload._tag === 'FromWebBridge') return
162
-
163
- const initialMessagePayloadDeferred = yield* Ref.get(initialMessagePayloadDeferredRef)
164
- const deferredAlreadyDone = yield* Deferred.isDone(initialMessagePayloadDeferred)
165
- const initialMessage = message.payload.initialMessage
166
-
167
- if (deferredAlreadyDone) {
168
- const previousInitialMessage = yield* Deferred.await(initialMessagePayloadDeferred)
169
- const messageSchema = WorkerSchema.LeaderWorkerInnerInitialMessage.pipe(
170
- Schema.omit('devtoolsEnabled', 'debugInstanceId'),
171
- )
172
- const isEqual = Schema.equivalence(messageSchema)
173
- if (isEqual(initialMessage, previousInitialMessage.initialMessage) === false) {
174
- const diff = Schema.debugDiff(messageSchema)(previousInitialMessage.initialMessage, initialMessage)
135
+ // Cache first-applied invariants to enforce stability across leader transitions
136
+ const InvariantsSchema = Schema.Struct({
137
+ storeId: Schema.String,
138
+ storageOptions: WorkerSchema.StorageType,
139
+ syncPayloadEncoded: Schema.UndefinedOr(Schema.JsonValue),
140
+ liveStoreVersion: Schema.Literal(liveStoreVersion),
141
+ devtoolsEnabled: Schema.Boolean,
142
+ })
143
+ type Invariants = typeof InvariantsSchema.Type
144
+ const invariantsRef = yield* Ref.make<Invariants | undefined>(undefined)
145
+ const sameInvariants = Schema.equivalence(InvariantsSchema)
175
146
 
176
- return yield* new UnexpectedError({
177
- cause: 'Initial message already sent and was different now',
178
- payload: {
179
- diff,
180
- previousInitialMessage: previousInitialMessage.initialMessage,
181
- newInitialMessage: initialMessage,
182
- },
183
- })
184
- }
185
- } else {
186
- yield* Deferred.succeed(initialMessagePayloadDeferred, message.payload)
187
- }
188
- }),
147
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- `SerializedRunner.Handlers` uses `any` in the R channel
148
+ return WorkerRunner.layerSerialized(WorkerSchema.SharedWorkerRequest, {
189
149
  // Whenever the client session leader changes (and thus creates a new leader thread), the new client session leader
190
150
  // sends a new MessagePort to the shared worker which proxies messages to the new leader thread.
191
- UpdateMessagePort: ({ port }) =>
151
+ UpdateMessagePort: ({ port, initial, liveStoreVersion: clientLiveStoreVersion }) =>
192
152
  Effect.gen(function* () {
193
- const initialMessagePayload = yield* initialMessagePayloadDeferredRef.get.pipe(Effect.andThen(Deferred.await))
153
+ // Enforce invariants: storeId, storageOptions, syncPayloadEncoded, liveStoreVersion must remain stable
154
+ const invariants: Invariants = {
155
+ storeId: initial.storeId,
156
+ storageOptions: initial.storageOptions,
157
+ syncPayloadEncoded: initial.syncPayloadEncoded,
158
+ liveStoreVersion: clientLiveStoreVersion,
159
+ devtoolsEnabled: initial.devtoolsEnabled,
160
+ }
161
+ const prev = yield* Ref.get(invariantsRef)
162
+ // Early return on mismatch to keep happy path linear
163
+ if (prev !== undefined && sameInvariants(prev, invariants) === false) {
164
+ const diff = Schema.debugDiff(InvariantsSchema)(prev, invariants)
165
+ return yield* new UnknownError({
166
+ cause: 'Store invariants changed across leader transitions',
167
+ payload: { diff, previous: prev, next: invariants },
168
+ })
169
+ }
170
+ // First writer records invariants
171
+ if (prev === undefined) {
172
+ yield* Ref.set(invariantsRef, invariants)
173
+ }
194
174
 
195
175
  yield* resetCurrentWorkerCtx
196
176
 
197
177
  const scope = yield* Scope.make()
198
178
 
199
179
  yield* Effect.gen(function* () {
200
- const shutdownChannel = yield* makeShutdownChannel(initialMessagePayload.initialMessage.storeId)
180
+ const shutdownChannel = yield* makeShutdownChannel(initial.storeId)
201
181
 
202
182
  yield* shutdownChannel.listen.pipe(
203
183
  Stream.flatten(),
@@ -212,7 +192,7 @@ const makeWorkerRunner = Effect.gen(function* () {
212
192
  const worker = yield* Worker.makePoolSerialized<WorkerSchema.LeaderWorkerInnerRequest>({
213
193
  size: 1,
214
194
  concurrency: 100,
215
- initialMessage: () => initialMessagePayload.initialMessage,
195
+ initialMessage: () => initial,
216
196
  }).pipe(
217
197
  Effect.provide(workerLayer),
218
198
  Effect.withSpan('@livestore/adapter-web:shared-worker:makeWorkerProxyFromPort'),
@@ -220,7 +200,7 @@ const makeWorkerRunner = Effect.gen(function* () {
220
200
 
221
201
  // Prepare the web mesh connection for leader worker to be able to connect to the devtools
222
202
  const { node } = yield* WebmeshWorker.CacheService
223
- const { storeId, clientId } = initialMessagePayload.initialMessage
203
+ const { storeId, clientId } = initial
224
204
 
225
205
  yield* DevtoolsWeb.connectViaWorker({
226
206
  node,
@@ -232,7 +212,6 @@ const makeWorkerRunner = Effect.gen(function* () {
232
212
  }).pipe(Effect.tapCauseLogPretty, Scope.extend(scope), Effect.forkIn(scope))
233
213
  }).pipe(
234
214
  Effect.withSpan('@livestore/adapter-web:shared-worker:updateMessagePort'),
235
- UnexpectedError.mapToUnexpectedError,
236
215
  Effect.tapCauseLogPretty,
237
216
  ),
238
217
 
@@ -240,12 +219,16 @@ const makeWorkerRunner = Effect.gen(function* () {
240
219
  BootStatusStream: forwardRequestStream,
241
220
  PushToLeader: forwardRequest,
242
221
  PullStream: forwardRequestStream,
222
+ StreamEvents: forwardRequestStream,
243
223
  Export: forwardRequest,
244
224
  GetRecreateSnapshot: forwardRequest,
245
225
  ExportEventlog: forwardRequest,
246
226
  Setup: forwardRequest,
247
227
  GetLeaderSyncState: forwardRequest,
228
+ SyncStateStream: forwardRequestStream,
248
229
  GetLeaderHead: forwardRequest,
230
+ GetNetworkStatus: forwardRequest,
231
+ NetworkStatusStream: forwardRequestStream,
249
232
  Shutdown: forwardRequest,
250
233
  ExtraDevtoolsMessage: forwardRequest,
251
234
 
@@ -254,7 +237,13 @@ const makeWorkerRunner = Effect.gen(function* () {
254
237
  })
255
238
  }).pipe(Layer.unwrapScoped)
256
239
 
257
- export const makeWorker = () => {
240
+ export const makeWorker = (options?: LogConfig.WithLoggerOptions): void => {
241
+ const runtimeLayer = Layer.mergeAll(
242
+ FetchHttpClient.layer,
243
+ WebmeshWorker.CacheService.layer({ nodeName: DevtoolsWeb.makeNodeName.sharedWorker({ storeId }) }),
244
+ )
245
+
246
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- propagated from `makeWorkerRunner`
258
247
  makeWorkerRunner.pipe(
259
248
  Layer.provide(BrowserWorkerRunner.layer),
260
249
  // WorkerRunner.launch,
@@ -262,13 +251,12 @@ export const makeWorker = () => {
262
251
  Effect.scoped,
263
252
  Effect.tapCauseLogPretty,
264
253
  Effect.annotateLogs({ thread: self.name }),
265
- Effect.provide(Logger.prettyWithThread(self.name)),
266
- Effect.provide(FetchHttpClient.layer),
267
- Effect.provide(WebmeshWorker.CacheService.layer({ nodeName: DevtoolsWeb.makeNodeName.sharedWorker({ storeId }) })),
268
- LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
254
+ Effect.provide(runtimeLayer),
255
+ LS_DEV === true ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
269
256
  // TODO remove type-cast (currently needed to silence a tsc bug)
270
- (_) => _ as any as Effect.Effect<void, any>,
271
- Logger.withMinimumLogLevel(LogLevel.Debug),
257
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- TSC bug workaround; the cast uses `any` as an intermediate
258
+ (_) => _ as any as Effect.Effect<void>,
259
+ LogConfig.withLoggerConfig(options, { threadName: self.name }),
272
260
  Effect.runFork,
273
261
  )
274
262
  }
@@ -1,5 +0,0 @@
1
- export declare const rootHandlePromise: Promise<FileSystemDirectoryHandle>;
2
- export declare const getDirHandle: (absDirPath: string | undefined) => Promise<FileSystemDirectoryHandle>;
3
- export declare const printTree: (directoryHandle_?: FileSystemDirectoryHandle | Promise<FileSystemDirectoryHandle>, depth?: number, prefix?: string) => Promise<void>;
4
- export declare const deleteAll: (directoryHandle: FileSystemDirectoryHandle) => Promise<void>;
5
- //# sourceMappingURL=opfs-utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"opfs-utils.d.ts","sourceRoot":"","sources":["../src/opfs-utils.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,iBAAiB,oCAYQ,CAAA;AAEtC,eAAO,MAAM,YAAY,GAAU,YAAY,MAAM,GAAG,SAAS,uCAWhE,CAAA;AAED,eAAO,MAAM,SAAS,GACpB,mBAAkB,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAqB,EACpG,QAAO,MAAiC,EACxC,eAAW,KACV,OAAO,CAAC,IAAI,CAgBd,CAAA;AAED,eAAO,MAAM,SAAS,GAAU,iBAAiB,yBAAyB,kBAMzE,CAAA"}
@@ -1,43 +0,0 @@
1
- // NOTE we're already firing off this promise call here since we'll need it anyway and need it cached
2
- import { prettyBytes } from '@livestore/utils';
3
- // To improve LiveStore compatibility with e.g. Node.js we're guarding for `navigator` / `navigator.storage` to be defined.
4
- export const rootHandlePromise = typeof navigator === 'undefined' || navigator.storage === undefined
5
- ? // We're using a proxy here to make the promise reject lazy
6
- new Proxy({}, {
7
- get: () => Promise.reject(new Error(`Can't get OPFS root handle in this environment as navigator.storage is undefined`)),
8
- })
9
- : navigator.storage.getDirectory();
10
- export const getDirHandle = async (absDirPath) => {
11
- const rootHandle = await rootHandlePromise;
12
- if (absDirPath === undefined)
13
- return rootHandle;
14
- let dirHandle = rootHandle;
15
- const directoryStack = absDirPath?.split('/').filter(Boolean);
16
- while (directoryStack.length > 0) {
17
- dirHandle = await dirHandle.getDirectoryHandle(directoryStack.shift());
18
- }
19
- return dirHandle;
20
- };
21
- export const printTree = async (directoryHandle_ = rootHandlePromise, depth = Number.POSITIVE_INFINITY, prefix = '') => {
22
- if (depth < 0)
23
- return;
24
- const directoryHandle = await directoryHandle_;
25
- const entries = directoryHandle.values();
26
- for await (const entry of entries) {
27
- const isDirectory = entry.kind === 'directory';
28
- const size = entry.kind === 'file' ? await entry.getFile().then((file) => prettyBytes(file.size)) : undefined;
29
- console.log(`${prefix}${isDirectory ? '📁' : '📄'} ${entry.name} ${size ? `(${size})` : ''}`);
30
- if (isDirectory) {
31
- const nestedDirectoryHandle = await directoryHandle.getDirectoryHandle(entry.name);
32
- await printTree(nestedDirectoryHandle, depth - 1, `${prefix} `);
33
- }
34
- }
35
- };
36
- export const deleteAll = async (directoryHandle) => {
37
- if (directoryHandle.kind !== 'directory')
38
- return;
39
- for await (const entryName of directoryHandle.keys()) {
40
- await directoryHandle.removeEntry(entryName, { recursive: true });
41
- }
42
- };
43
- //# sourceMappingURL=opfs-utils.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"opfs-utils.js","sourceRoot":"","sources":["../src/opfs-utils.ts"],"names":[],"mappings":"AAAA,qGAAqG;AAErG,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,2HAA2H;AAC3H,MAAM,CAAC,MAAM,iBAAiB,GAC5B,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,CAAC,OAAO,KAAK,SAAS;IACjE,CAAC,CAAC,2DAA2D;QAC1D,IAAI,KAAK,CACR,EAAE,EACF;YACE,GAAG,EAAE,GAAG,EAAE,CACR,OAAO,CAAC,MAAM,CACZ,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAC9F;SACJ,CACQ;IACb,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,CAAA;AAEtC,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,UAA8B,EAAE,EAAE;IACnE,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAA;IAC1C,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,UAAU,CAAA;IAE/C,IAAI,SAAS,GAAG,UAAU,CAAA;IAC1B,MAAM,cAAc,GAAG,UAAU,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC7D,OAAO,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,SAAS,GAAG,MAAM,SAAS,CAAC,kBAAkB,CAAC,cAAc,CAAC,KAAK,EAAG,CAAC,CAAA;IACzE,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,mBAAmF,iBAAiB,EACpG,QAAgB,MAAM,CAAC,iBAAiB,EACxC,MAAM,GAAG,EAAE,EACI,EAAE;IACjB,IAAI,KAAK,GAAG,CAAC;QAAE,OAAM;IAErB,MAAM,eAAe,GAAG,MAAM,gBAAgB,CAAA;IAC9C,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,EAAE,CAAA;IAExC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAClC,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,KAAK,WAAW,CAAA;QAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAC7G,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QAE7F,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,qBAAqB,GAAG,MAAM,eAAe,CAAC,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAClF,MAAM,SAAS,CAAC,qBAAqB,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,MAAM,IAAI,CAAC,CAAA;QAClE,CAAC;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,eAA0C,EAAE,EAAE;IAC5E,IAAI,eAAe,CAAC,IAAI,KAAK,WAAW;QAAE,OAAM;IAEhD,IAAI,KAAK,EAAE,MAAM,SAAS,IAAI,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC;QACrD,MAAM,eAAe,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACnE,CAAC;AACH,CAAC,CAAA"}
package/src/opfs-utils.ts DELETED
@@ -1,61 +0,0 @@
1
- // NOTE we're already firing off this promise call here since we'll need it anyway and need it cached
2
-
3
- import { prettyBytes } from '@livestore/utils'
4
-
5
- // To improve LiveStore compatibility with e.g. Node.js we're guarding for `navigator` / `navigator.storage` to be defined.
6
- export const rootHandlePromise =
7
- typeof navigator === 'undefined' || navigator.storage === undefined
8
- ? // We're using a proxy here to make the promise reject lazy
9
- (new Proxy(
10
- {},
11
- {
12
- get: () =>
13
- Promise.reject(
14
- new Error(`Can't get OPFS root handle in this environment as navigator.storage is undefined`),
15
- ),
16
- },
17
- ) as never)
18
- : navigator.storage.getDirectory()
19
-
20
- export const getDirHandle = async (absDirPath: string | undefined) => {
21
- const rootHandle = await rootHandlePromise
22
- if (absDirPath === undefined) return rootHandle
23
-
24
- let dirHandle = rootHandle
25
- const directoryStack = absDirPath?.split('/').filter(Boolean)
26
- while (directoryStack.length > 0) {
27
- dirHandle = await dirHandle.getDirectoryHandle(directoryStack.shift()!)
28
- }
29
-
30
- return dirHandle
31
- }
32
-
33
- export const printTree = async (
34
- directoryHandle_: FileSystemDirectoryHandle | Promise<FileSystemDirectoryHandle> = rootHandlePromise,
35
- depth: number = Number.POSITIVE_INFINITY,
36
- prefix = '',
37
- ): Promise<void> => {
38
- if (depth < 0) return
39
-
40
- const directoryHandle = await directoryHandle_
41
- const entries = directoryHandle.values()
42
-
43
- for await (const entry of entries) {
44
- const isDirectory = entry.kind === 'directory'
45
- const size = entry.kind === 'file' ? await entry.getFile().then((file) => prettyBytes(file.size)) : undefined
46
- console.log(`${prefix}${isDirectory ? '📁' : '📄'} ${entry.name} ${size ? `(${size})` : ''}`)
47
-
48
- if (isDirectory) {
49
- const nestedDirectoryHandle = await directoryHandle.getDirectoryHandle(entry.name)
50
- await printTree(nestedDirectoryHandle, depth - 1, `${prefix} `)
51
- }
52
- }
53
- }
54
-
55
- export const deleteAll = async (directoryHandle: FileSystemDirectoryHandle) => {
56
- if (directoryHandle.kind !== 'directory') return
57
-
58
- for await (const entryName of directoryHandle.keys()) {
59
- await directoryHandle.removeEntry(entryName, { recursive: true })
60
- }
61
- }