@effect/platform-browser 4.0.0-beta.7 → 4.0.0-beta.71

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 (91) hide show
  1. package/dist/BrowserCrypto.d.ts +81 -0
  2. package/dist/BrowserCrypto.d.ts.map +1 -0
  3. package/dist/BrowserCrypto.js +119 -0
  4. package/dist/BrowserCrypto.js.map +1 -0
  5. package/dist/BrowserHttpClient.d.ts +40 -16
  6. package/dist/BrowserHttpClient.d.ts.map +1 -1
  7. package/dist/BrowserHttpClient.js +74 -18
  8. package/dist/BrowserHttpClient.js.map +1 -1
  9. package/dist/BrowserKeyValueStore.d.ts +37 -14
  10. package/dist/BrowserKeyValueStore.d.ts.map +1 -1
  11. package/dist/BrowserKeyValueStore.js +185 -10
  12. package/dist/BrowserKeyValueStore.js.map +1 -1
  13. package/dist/BrowserPersistence.d.ts +40 -0
  14. package/dist/BrowserPersistence.d.ts.map +1 -0
  15. package/dist/BrowserPersistence.js +207 -0
  16. package/dist/BrowserPersistence.js.map +1 -0
  17. package/dist/BrowserRuntime.d.ts +79 -4
  18. package/dist/BrowserRuntime.d.ts.map +1 -1
  19. package/dist/BrowserRuntime.js +19 -1
  20. package/dist/BrowserRuntime.js.map +1 -1
  21. package/dist/BrowserSocket.d.ts +64 -6
  22. package/dist/BrowserSocket.d.ts.map +1 -1
  23. package/dist/BrowserSocket.js +64 -6
  24. package/dist/BrowserSocket.js.map +1 -1
  25. package/dist/BrowserStream.d.ts +48 -5
  26. package/dist/BrowserStream.d.ts.map +1 -1
  27. package/dist/BrowserStream.js +48 -5
  28. package/dist/BrowserStream.js.map +1 -1
  29. package/dist/BrowserWorker.d.ts +25 -4
  30. package/dist/BrowserWorker.d.ts.map +1 -1
  31. package/dist/BrowserWorker.js +62 -6
  32. package/dist/BrowserWorker.js.map +1 -1
  33. package/dist/BrowserWorkerRunner.d.ts +31 -6
  34. package/dist/BrowserWorkerRunner.d.ts.map +1 -1
  35. package/dist/BrowserWorkerRunner.js +65 -10
  36. package/dist/BrowserWorkerRunner.js.map +1 -1
  37. package/dist/Clipboard.d.ts +77 -13
  38. package/dist/Clipboard.d.ts.map +1 -1
  39. package/dist/Clipboard.js +57 -11
  40. package/dist/Clipboard.js.map +1 -1
  41. package/dist/Geolocation.d.ts +101 -24
  42. package/dist/Geolocation.d.ts.map +1 -1
  43. package/dist/Geolocation.js +69 -16
  44. package/dist/Geolocation.js.map +1 -1
  45. package/dist/IndexedDb.d.ts +89 -0
  46. package/dist/IndexedDb.d.ts.map +1 -0
  47. package/dist/IndexedDb.js +99 -0
  48. package/dist/IndexedDb.js.map +1 -0
  49. package/dist/IndexedDbDatabase.d.ts +169 -0
  50. package/dist/IndexedDbDatabase.d.ts.map +1 -0
  51. package/dist/IndexedDbDatabase.js +343 -0
  52. package/dist/IndexedDbDatabase.js.map +1 -0
  53. package/dist/IndexedDbQueryBuilder.d.ts +438 -0
  54. package/dist/IndexedDbQueryBuilder.d.ts.map +1 -0
  55. package/dist/IndexedDbQueryBuilder.js +935 -0
  56. package/dist/IndexedDbQueryBuilder.js.map +1 -0
  57. package/dist/IndexedDbTable.d.ts +191 -0
  58. package/dist/IndexedDbTable.d.ts.map +1 -0
  59. package/dist/IndexedDbTable.js +96 -0
  60. package/dist/IndexedDbTable.js.map +1 -0
  61. package/dist/IndexedDbVersion.d.ts +94 -0
  62. package/dist/IndexedDbVersion.d.ts.map +1 -0
  63. package/dist/IndexedDbVersion.js +39 -0
  64. package/dist/IndexedDbVersion.js.map +1 -0
  65. package/dist/Permissions.d.ts +76 -16
  66. package/dist/Permissions.d.ts.map +1 -1
  67. package/dist/Permissions.js +66 -11
  68. package/dist/Permissions.js.map +1 -1
  69. package/dist/index.d.ts +38 -10
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +38 -10
  72. package/dist/index.js.map +1 -1
  73. package/package.json +4 -3
  74. package/src/BrowserCrypto.ts +129 -0
  75. package/src/BrowserHttpClient.ts +84 -24
  76. package/src/BrowserKeyValueStore.ts +201 -12
  77. package/src/BrowserPersistence.ts +376 -0
  78. package/src/BrowserRuntime.ts +79 -4
  79. package/src/BrowserSocket.ts +64 -6
  80. package/src/BrowserStream.ts +48 -5
  81. package/src/BrowserWorker.ts +62 -6
  82. package/src/BrowserWorkerRunner.ts +65 -10
  83. package/src/Clipboard.ts +74 -13
  84. package/src/Geolocation.ts +97 -20
  85. package/src/IndexedDb.ts +127 -0
  86. package/src/IndexedDbDatabase.ts +664 -0
  87. package/src/IndexedDbQueryBuilder.ts +2048 -0
  88. package/src/IndexedDbTable.ts +285 -0
  89. package/src/IndexedDbVersion.ts +133 -0
  90. package/src/Permissions.ts +71 -14
  91. package/src/index.ts +45 -10
@@ -0,0 +1,664 @@
1
+ /**
2
+ * Builds and opens typed IndexedDB databases from versioned schema migrations.
3
+ *
4
+ * This module turns an `IndexedDbVersion` migration chain into an
5
+ * `IndexedDbDatabase` layer. The layer opens the browser database, runs any
6
+ * pending upgrade migrations, provides a query builder for the current schema,
7
+ * and exposes a `rebuild` effect that deletes and reopens the database. It is
8
+ * the database-level companion to the table, version, and query builder
9
+ * modules.
10
+ *
11
+ * Use it for browser-local persistence such as offline-first application
12
+ * state, cached server data, background queues, drafts, and other client-side
13
+ * stores that need typed reads and writes backed by IndexedDB transactions.
14
+ *
15
+ * IndexedDB schema changes can only happen inside upgrade transactions, so
16
+ * every call to `make` or `.add` represents the next browser database version
17
+ * and only migrations after the existing browser version are run. Table and
18
+ * index definitions type the migration and query APIs, but object stores and
19
+ * indexes still need to be created or removed explicitly with the migration
20
+ * transaction helpers. Include the complete target table set in each version,
21
+ * create indexes before querying them, and treat key path or auto-increment
22
+ * changes as store migrations that copy data into a replacement object store.
23
+ * Upgrades can be blocked by other open connections, and all migration reads,
24
+ * writes, store changes, and index changes share the single upgrade
25
+ * transaction supplied by the browser.
26
+ *
27
+ * @since 4.0.0
28
+ */
29
+ import * as Context from "effect/Context"
30
+ import * as Data from "effect/Data"
31
+ import * as Effect from "effect/Effect"
32
+ import * as Effectable from "effect/Effectable"
33
+ import * as Fiber from "effect/Fiber"
34
+ import * as Layer from "effect/Layer"
35
+ import * as MutableRef from "effect/MutableRef"
36
+ import * as Semaphore from "effect/Semaphore"
37
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity"
38
+ import * as IndexedDb from "./IndexedDb.ts"
39
+ import * as IndexedDbQueryBuilder from "./IndexedDbQueryBuilder.ts"
40
+ import type * as IndexedDbTable from "./IndexedDbTable.ts"
41
+ import type * as IndexedDbVersion from "./IndexedDbVersion.ts"
42
+
43
+ const TypeId = "~@effect/platform-browser/IndexedDbDatabase"
44
+ const ErrorTypeId = "~@effect/platform-browser/IndexedDbDatabase/IndexedDbDatabaseError"
45
+
46
+ const SchemaProto = {
47
+ [TypeId]: {
48
+ _A: (_: never) => _
49
+ },
50
+ ...Effectable.Prototype<IndexedDbSchema<any, any, any>>({
51
+ label: "IndexedDbSchema",
52
+ evaluate() {
53
+ return this.getQueryBuilder
54
+ }
55
+ }),
56
+ get getQueryBuilder() {
57
+ const self = this as unknown as IndexedDbSchema<any, any, any>
58
+ return IndexedDbDatabase.useSync(({ database, IDBKeyRange, reactivity }) =>
59
+ IndexedDbQueryBuilder.make({
60
+ database,
61
+ IDBKeyRange,
62
+ tables: self.version.tables,
63
+ reactivity
64
+ })
65
+ )
66
+ },
67
+ add<Version extends IndexedDbVersion.AnyWithProps>(
68
+ this: IndexedDbSchema<any, any, any>,
69
+ version: Version,
70
+ migrate: (
71
+ fromQuery: Transaction<any>,
72
+ toQuery: Transaction<Version>
73
+ ) => Effect.Effect<void, Error>
74
+ ) {
75
+ return makeMigration({
76
+ fromVersion: this.version,
77
+ version,
78
+ migrate,
79
+ previous: this
80
+ })
81
+ },
82
+ layer(this: IndexedDbSchema<any, any, any>, databaseName: string) {
83
+ return layer(databaseName, this)
84
+ }
85
+ }
86
+
87
+ /**
88
+ * String union describing the failure categories for IndexedDB database opening, migration, and schema operations.
89
+ *
90
+ * @category errors
91
+ * @since 4.0.0
92
+ */
93
+ export type ErrorReason =
94
+ | "TransactionError"
95
+ | "MissingTable"
96
+ | "OpenError"
97
+ | "UpgradeError"
98
+ | "Aborted"
99
+ | "Blocked"
100
+ | "MissingIndex"
101
+
102
+ /**
103
+ * Tagged error for IndexedDB database operations, carrying a database error reason and the original cause.
104
+ *
105
+ * @category errors
106
+ * @since 4.0.0
107
+ */
108
+ export class IndexedDbDatabaseError extends Data.TaggedError(
109
+ "IndexedDbDatabaseError"
110
+ )<{
111
+ reason: ErrorReason
112
+ cause: unknown
113
+ }> {
114
+ /**
115
+ * Marks this value as an IndexedDB database error for runtime guards.
116
+ *
117
+ * @since 4.0.0
118
+ */
119
+ readonly [ErrorTypeId]: typeof ErrorTypeId = ErrorTypeId
120
+ override readonly message = this.reason
121
+ }
122
+
123
+ /**
124
+ * Service tag for an open IndexedDB database, its `IDBKeyRange` constructor, reactivity service, and rebuild effect.
125
+ *
126
+ * **When to use**
127
+ *
128
+ * Use when an effect needs access to the live database service after an
129
+ * `IndexedDbSchema` layer has been provided, especially for `rebuild` or
130
+ * lower-level database primitives.
131
+ *
132
+ * **Details**
133
+ *
134
+ * `database` is a mutable reference to the current `IDBDatabase`. `IDBKeyRange`
135
+ * and `reactivity` are shared with query builders created from the schema.
136
+ *
137
+ * **Gotchas**
138
+ *
139
+ * `rebuild` closes and deletes the browser database, then reopens it and reruns
140
+ * migrations. Records not recreated by migrations are removed.
141
+ *
142
+ * @see {@link IndexedDb.IndexedDb} for the lower-level browser IndexedDB primitives
143
+ * @see {@link make} for creating a schema that provides this service as a layer
144
+ *
145
+ * @category models
146
+ * @since 4.0.0
147
+ */
148
+ export class IndexedDbDatabase extends Context.Service<
149
+ IndexedDbDatabase,
150
+ {
151
+ readonly database: MutableRef.MutableRef<globalThis.IDBDatabase>
152
+ readonly IDBKeyRange: typeof globalThis.IDBKeyRange
153
+ readonly reactivity: Reactivity.Reactivity["Service"]
154
+ readonly rebuild: Effect.Effect<void, IndexedDbDatabaseError>
155
+ }
156
+ >()(TypeId) {}
157
+
158
+ /**
159
+ * Describes an IndexedDB schema version and its migrations, and acts as an effect that yields a query builder for the target version.
160
+ *
161
+ * @category models
162
+ * @since 4.0.0
163
+ */
164
+ export interface IndexedDbSchema<
165
+ in out FromVersion extends IndexedDbVersion.AnyWithProps,
166
+ in out ToVersion extends IndexedDbVersion.AnyWithProps,
167
+ out Error = never
168
+ > extends
169
+ Effect.Effect<
170
+ IndexedDbQueryBuilder.IndexedDbQueryBuilder<ToVersion>,
171
+ never,
172
+ IndexedDbDatabase
173
+ >
174
+ {
175
+ new(_: never): {}
176
+
177
+ readonly previous: [FromVersion] extends [never] ? undefined
178
+ : IndexedDbSchema<never, FromVersion, Error>
179
+ readonly fromVersion: FromVersion
180
+ readonly version: ToVersion
181
+
182
+ readonly migrate: [FromVersion] extends [never] ? (query: Transaction<ToVersion>) => Effect.Effect<void, Error>
183
+ : (
184
+ fromQuery: Transaction<FromVersion>,
185
+ toQuery: Transaction<ToVersion>
186
+ ) => Effect.Effect<void, Error>
187
+
188
+ readonly add: <Version extends IndexedDbVersion.AnyWithProps, MigrationError>(
189
+ version: Version,
190
+ migrate: (
191
+ fromQuery: Transaction<ToVersion>,
192
+ toQuery: Transaction<Version>
193
+ ) => Effect.Effect<void, MigrationError>
194
+ ) => IndexedDbSchema<ToVersion, Version, MigrationError | Error>
195
+
196
+ readonly getQueryBuilder: Effect.Effect<
197
+ IndexedDbQueryBuilder.IndexedDbQueryBuilder<ToVersion>,
198
+ never,
199
+ IndexedDbDatabase
200
+ >
201
+
202
+ readonly layer: (
203
+ databaseName: string
204
+ ) => Layer.Layer<
205
+ IndexedDbDatabase,
206
+ IndexedDbDatabaseError,
207
+ IndexedDb.IndexedDb
208
+ >
209
+ }
210
+
211
+ /**
212
+ * Query builder available during a database migration, extended with object-store and index management helpers for the active `IDBTransaction`.
213
+ *
214
+ * @category models
215
+ * @since 4.0.0
216
+ */
217
+ export interface Transaction<
218
+ Source extends IndexedDbVersion.AnyWithProps = never
219
+ > extends Omit<IndexedDbQueryBuilder.IndexedDbQueryBuilder<Source>, "transaction"> {
220
+ readonly transaction: globalThis.IDBTransaction
221
+
222
+ readonly createObjectStore: <
223
+ A extends IndexedDbTable.TableName<IndexedDbVersion.Tables<Source>>
224
+ >(
225
+ table: A
226
+ ) => Effect.Effect<globalThis.IDBObjectStore, IndexedDbDatabaseError>
227
+
228
+ readonly deleteObjectStore: <
229
+ A extends IndexedDbTable.TableName<IndexedDbVersion.Tables<Source>>
230
+ >(
231
+ table: A
232
+ ) => Effect.Effect<void, IndexedDbDatabaseError>
233
+
234
+ readonly createIndex: <
235
+ Name extends IndexedDbTable.TableName<IndexedDbVersion.Tables<Source>>
236
+ >(
237
+ table: Name,
238
+ indexName: IndexFromTableName<Source, Name>,
239
+ options?: IDBIndexParameters
240
+ ) => Effect.Effect<globalThis.IDBIndex, IndexedDbDatabaseError>
241
+
242
+ readonly deleteIndex: <
243
+ Name extends IndexedDbTable.TableName<IndexedDbVersion.Tables<Source>>
244
+ >(
245
+ table: Name,
246
+ indexName: IndexFromTableName<Source, Name>
247
+ ) => Effect.Effect<void, IndexedDbDatabaseError>
248
+ }
249
+
250
+ /**
251
+ * Extracts the string-literal index names defined by an `IndexedDbTable`.
252
+ *
253
+ * @category models
254
+ * @since 4.0.0
255
+ */
256
+ export type IndexFromTable<Table extends IndexedDbTable.AnyWithProps> = IsStringLiteral<
257
+ Extract<keyof IndexedDbTable.Indexes<Table>, string>
258
+ > extends true ? Extract<keyof IndexedDbTable.Indexes<Table>, string>
259
+ : never
260
+
261
+ /**
262
+ * Extracts the valid index names for a table name within an IndexedDB version.
263
+ *
264
+ * @category models
265
+ * @since 4.0.0
266
+ */
267
+ export type IndexFromTableName<
268
+ Version extends IndexedDbVersion.AnyWithProps,
269
+ Table extends string
270
+ > = IndexFromTable<
271
+ IndexedDbTable.WithName<IndexedDbVersion.Tables<Version>, Table>
272
+ >
273
+
274
+ /**
275
+ * Type-erased IndexedDB schema shape used when traversing schema migration chains.
276
+ *
277
+ * @category models
278
+ * @since 4.0.0
279
+ */
280
+ export interface Any {
281
+ readonly previous?: Any | undefined
282
+ readonly layer: (
283
+ databaseName: string
284
+ ) => Layer.Layer<
285
+ IndexedDbDatabase,
286
+ IndexedDbDatabaseError,
287
+ IndexedDb.IndexedDb
288
+ >
289
+ }
290
+
291
+ /**
292
+ * Type-erased `IndexedDbSchema` covering any source version, target version, and migration error type.
293
+ *
294
+ * @category models
295
+ * @since 4.0.0
296
+ */
297
+ export type AnySchema = IndexedDbSchema<
298
+ IndexedDbVersion.AnyWithProps,
299
+ IndexedDbVersion.AnyWithProps,
300
+ any
301
+ >
302
+
303
+ /**
304
+ * Creates the initial `IndexedDbSchema` from a version and an initialization migration run during database upgrade.
305
+ *
306
+ * @category constructors
307
+ * @since 4.0.0
308
+ */
309
+ export const make = <
310
+ InitialVersion extends IndexedDbVersion.AnyWithProps,
311
+ Error
312
+ >(
313
+ initialVersion: InitialVersion,
314
+ init: (toQuery: Transaction<InitialVersion>) => Effect.Effect<void, Error>
315
+ ): IndexedDbSchema<never, InitialVersion, Error> => {
316
+ // oxlint-disable-next-line typescript/no-extraneous-class
317
+ function Initial() {}
318
+ Object.setPrototypeOf(Initial, SchemaProto)
319
+ ;(Initial as any).version = initialVersion
320
+ ;(Initial as any).migrate = init
321
+ return Initial as any
322
+ }
323
+
324
+ const makeMigration = <
325
+ FromVersion extends IndexedDbVersion.AnyWithProps,
326
+ ToVersion extends IndexedDbVersion.AnyWithProps,
327
+ Error
328
+ >(options: {
329
+ readonly previous:
330
+ | IndexedDbSchema<FromVersion, ToVersion, Error>
331
+ | IndexedDbSchema<never, FromVersion, Error>
332
+ readonly fromVersion: FromVersion
333
+ readonly version: ToVersion
334
+ readonly migrate: (
335
+ fromQuery: Transaction<FromVersion>,
336
+ toQuery: Transaction<ToVersion>
337
+ ) => Effect.Effect<void, Error>
338
+ }): IndexedDbSchema<FromVersion, ToVersion, Error> => {
339
+ // oxlint-disable-next-line typescript/no-extraneous-class
340
+ function Migration() {}
341
+ Object.setPrototypeOf(Migration, SchemaProto)
342
+ ;(Migration as any).previous = options.previous
343
+ ;(Migration as any).fromVersion = options.fromVersion
344
+ ;(Migration as any).version = options.version
345
+ ;(Migration as any).migrate = options.migrate
346
+
347
+ return Migration as any
348
+ }
349
+
350
+ const layer = <DatabaseName extends string>(
351
+ databaseName: DatabaseName,
352
+ migration: Any
353
+ ) =>
354
+ Layer.effect(
355
+ IndexedDbDatabase,
356
+ Effect.gen(function*() {
357
+ const { IDBKeyRange, indexedDB } = yield* IndexedDb.IndexedDb
358
+ const reactivity = yield* Reactivity.Reactivity
359
+ const context = yield* Effect.context()
360
+ const runForkWith = Effect.runForkWith(context)
361
+
362
+ let oldVersion = 0
363
+ const migrations: Array<Any> = []
364
+ let current = migration
365
+ while (current) {
366
+ migrations.unshift(current)
367
+ current = (current as unknown as AnySchema).previous as any
368
+ }
369
+
370
+ const version = migrations.length
371
+ const database = MutableRef.make<globalThis.IDBDatabase>(null as any)
372
+
373
+ const open = Effect.callback<
374
+ void,
375
+ IndexedDbDatabaseError
376
+ >((resume) => {
377
+ const request = indexedDB.open(databaseName, version)
378
+
379
+ request.onblocked = (event) => {
380
+ resume(
381
+ Effect.fail(
382
+ new IndexedDbDatabaseError({
383
+ reason: "Blocked",
384
+ cause: event
385
+ })
386
+ )
387
+ )
388
+ }
389
+
390
+ request.onerror = (event) => {
391
+ const idbRequest = event.target as IDBRequest<IDBDatabase>
392
+
393
+ resume(
394
+ Effect.fail(
395
+ new IndexedDbDatabaseError({
396
+ reason: "OpenError",
397
+ cause: idbRequest.error
398
+ })
399
+ )
400
+ )
401
+ }
402
+
403
+ let fiber: Fiber.Fiber<void, IndexedDbDatabaseError> | undefined
404
+ request.onupgradeneeded = (event) => {
405
+ const idbRequest = event.target as IDBRequest<IDBDatabase>
406
+ const db = idbRequest.result
407
+ const transaction = idbRequest.transaction
408
+ oldVersion = event.oldVersion
409
+
410
+ MutableRef.set(database, db)
411
+
412
+ if (transaction === null) {
413
+ return resume(
414
+ Effect.fail(
415
+ new IndexedDbDatabaseError({
416
+ reason: "TransactionError",
417
+ cause: null
418
+ })
419
+ )
420
+ )
421
+ }
422
+
423
+ transaction.onabort = (event) => {
424
+ resume(
425
+ Effect.fail(
426
+ new IndexedDbDatabaseError({
427
+ reason: "Aborted",
428
+ cause: event
429
+ })
430
+ )
431
+ )
432
+ }
433
+
434
+ transaction.onerror = (event) => {
435
+ resume(
436
+ Effect.fail(
437
+ new IndexedDbDatabaseError({
438
+ reason: "TransactionError",
439
+ cause: event
440
+ })
441
+ )
442
+ )
443
+ }
444
+
445
+ const effect = Effect.forEach(
446
+ migrations.slice(oldVersion),
447
+ (untypedMigration) => {
448
+ if (untypedMigration.previous === undefined) {
449
+ const migration = untypedMigration as any as AnySchema
450
+ const api = makeTransactionProto({
451
+ database,
452
+ IDBKeyRange,
453
+ tables: migration.version.tables,
454
+ transaction,
455
+ reactivity
456
+ })
457
+ return (migration as any).migrate(api) as Effect.Effect<
458
+ void,
459
+ IndexedDbDatabaseError
460
+ >
461
+ } else if (untypedMigration.previous) {
462
+ const migration = untypedMigration as any as AnySchema
463
+ const fromApi = makeTransactionProto({
464
+ database,
465
+ IDBKeyRange,
466
+ tables: migration.fromVersion.tables,
467
+ transaction,
468
+ reactivity
469
+ })
470
+ const toApi = makeTransactionProto({
471
+ database,
472
+ IDBKeyRange,
473
+ tables: migration.version.tables,
474
+ transaction,
475
+ reactivity
476
+ })
477
+ return migration.migrate(fromApi, toApi) as Effect.Effect<
478
+ void,
479
+ IndexedDbDatabaseError
480
+ >
481
+ }
482
+
483
+ return Effect.die(new Error("Invalid migration"))
484
+ },
485
+ { discard: true }
486
+ ).pipe(
487
+ Effect.mapError(
488
+ (cause) =>
489
+ new IndexedDbDatabaseError({
490
+ reason: "UpgradeError",
491
+ cause
492
+ })
493
+ ),
494
+ Effect.provideService(IndexedDbQueryBuilder.IndexedDbTransaction, transaction)
495
+ )
496
+ fiber = runForkWith(effect)
497
+ fiber.currentDispatcher.flush()
498
+ }
499
+
500
+ request.onsuccess = (event) => {
501
+ const idbRequest = event.target as IDBRequest<IDBDatabase>
502
+ const db = idbRequest.result
503
+ MutableRef.set(database, db)
504
+ if (fiber) {
505
+ // ensure migration errors are propagated
506
+ resume(Effect.asVoid(Fiber.join(fiber)))
507
+ } else {
508
+ resume(Effect.void)
509
+ }
510
+ }
511
+ })
512
+
513
+ yield* Effect.addFinalizer(() => {
514
+ database.current?.close()
515
+ return Effect.void
516
+ })
517
+ yield* open
518
+
519
+ const rebuildLock = Semaphore.makeUnsafe(1).withPermit
520
+ const rebuild = Effect.callback<void, IndexedDbDatabaseError>((resume) => {
521
+ database.current?.close()
522
+ const request = indexedDB.deleteDatabase(databaseName)
523
+ request.onerror = (_) => {
524
+ resume(
525
+ Effect.fail(
526
+ new IndexedDbDatabaseError({
527
+ reason: "OpenError",
528
+ cause: request.error
529
+ })
530
+ )
531
+ )
532
+ }
533
+ request.onsuccess = () => {
534
+ resume(Effect.void)
535
+ }
536
+ }).pipe(
537
+ Effect.flatMap(() => open),
538
+ rebuildLock
539
+ )
540
+
541
+ return IndexedDbDatabase.of({ database, IDBKeyRange, rebuild, reactivity })
542
+ })
543
+ ).pipe(
544
+ Layer.provide(Reactivity.layer)
545
+ )
546
+
547
+ // -----------------------------------------------------------------------------
548
+ // Internal
549
+ // -----------------------------------------------------------------------------
550
+
551
+ type IsStringLiteral<T> = T extends string ? string extends T ? false
552
+ : true
553
+ : false
554
+
555
+ const makeTransactionProto = <Source extends IndexedDbVersion.AnyWithProps>({
556
+ IDBKeyRange,
557
+ database,
558
+ tables,
559
+ transaction,
560
+ reactivity
561
+ }: {
562
+ readonly database: MutableRef.MutableRef<globalThis.IDBDatabase>
563
+ readonly IDBKeyRange: typeof globalThis.IDBKeyRange
564
+ readonly tables: ReadonlyMap<string, IndexedDbVersion.Tables<Source>>
565
+ readonly transaction: globalThis.IDBTransaction
566
+ readonly reactivity: Reactivity.Reactivity["Service"]
567
+ }): Transaction<Source> => {
568
+ const migration = IndexedDbQueryBuilder.make({
569
+ database,
570
+ IDBKeyRange,
571
+ tables,
572
+ reactivity
573
+ }) as any
574
+
575
+ migration.transaction = transaction
576
+
577
+ migration.createObjectStore = Effect.fnUntraced(function*(table: string) {
578
+ const createTable = yield* Effect.fromNullishOr(tables.get(table)).pipe(
579
+ Effect.mapError(
580
+ (cause) =>
581
+ new IndexedDbDatabaseError({
582
+ reason: "MissingTable",
583
+ cause
584
+ })
585
+ )
586
+ )
587
+
588
+ return yield* Effect.try({
589
+ try: () =>
590
+ database.current.createObjectStore(createTable.tableName, {
591
+ keyPath: createTable.keyPath,
592
+ autoIncrement: createTable.autoIncrement
593
+ }),
594
+ catch: (cause) =>
595
+ new IndexedDbDatabaseError({
596
+ reason: "TransactionError",
597
+ cause
598
+ })
599
+ })
600
+ })
601
+
602
+ migration.deleteObjectStore = Effect.fnUntraced(function*(table: string) {
603
+ const createTable = yield* Effect.fromNullishOr(tables.get(table)).pipe(
604
+ Effect.mapError(
605
+ (cause) =>
606
+ new IndexedDbDatabaseError({
607
+ reason: "MissingTable",
608
+ cause
609
+ })
610
+ )
611
+ )
612
+
613
+ return yield* Effect.try({
614
+ try: () => database.current.deleteObjectStore(createTable.tableName),
615
+ catch: (cause) =>
616
+ new IndexedDbDatabaseError({
617
+ reason: "TransactionError",
618
+ cause
619
+ })
620
+ })
621
+ })
622
+
623
+ migration.createIndex = Effect.fnUntraced(function*(
624
+ table: string,
625
+ indexName: string,
626
+ options?: IDBIndexParameters
627
+ ) {
628
+ const store = transaction.objectStore(table)
629
+ const sourceTable = tables.get(table)!
630
+
631
+ const keyPath = yield* Effect.fromNullishOr(
632
+ sourceTable.indexes[indexName]
633
+ ).pipe(
634
+ Effect.mapError(
635
+ (cause) =>
636
+ new IndexedDbDatabaseError({
637
+ reason: "MissingIndex",
638
+ cause
639
+ })
640
+ )
641
+ )
642
+
643
+ return yield* Effect.try({
644
+ try: () => store.createIndex(indexName, keyPath, options),
645
+ catch: (cause) =>
646
+ new IndexedDbDatabaseError({
647
+ reason: "TransactionError",
648
+ cause
649
+ })
650
+ })
651
+ })
652
+
653
+ migration.deleteIndex = (table: string, indexName: string) =>
654
+ Effect.try({
655
+ try: () => transaction.objectStore(table).deleteIndex(indexName),
656
+ catch: (cause) =>
657
+ new IndexedDbDatabaseError({
658
+ reason: "TransactionError",
659
+ cause
660
+ })
661
+ })
662
+
663
+ return migration
664
+ }