@effect/platform-browser 4.0.0-beta.9 → 4.0.0-beta.91

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