@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.
- package/dist/BrowserCrypto.d.ts +49 -0
- package/dist/BrowserCrypto.d.ts.map +1 -0
- package/dist/BrowserCrypto.js +87 -0
- package/dist/BrowserCrypto.js.map +1 -0
- package/dist/BrowserHttpClient.d.ts +48 -19
- package/dist/BrowserHttpClient.d.ts.map +1 -1
- package/dist/BrowserHttpClient.js +62 -21
- package/dist/BrowserHttpClient.js.map +1 -1
- package/dist/BrowserKeyValueStore.d.ts +37 -14
- package/dist/BrowserKeyValueStore.d.ts.map +1 -1
- package/dist/BrowserKeyValueStore.js +144 -10
- package/dist/BrowserKeyValueStore.js.map +1 -1
- package/dist/BrowserPersistence.d.ts +40 -0
- package/dist/BrowserPersistence.d.ts.map +1 -0
- package/dist/BrowserPersistence.js +207 -0
- package/dist/BrowserPersistence.js.map +1 -0
- package/dist/BrowserRuntime.d.ts +64 -4
- package/dist/BrowserRuntime.d.ts.map +1 -1
- package/dist/BrowserRuntime.js +19 -1
- package/dist/BrowserRuntime.js.map +1 -1
- package/dist/BrowserSocket.d.ts +33 -6
- package/dist/BrowserSocket.d.ts.map +1 -1
- package/dist/BrowserSocket.js +33 -6
- package/dist/BrowserSocket.js.map +1 -1
- package/dist/BrowserStream.d.ts +19 -7
- package/dist/BrowserStream.d.ts.map +1 -1
- package/dist/BrowserStream.js +19 -7
- package/dist/BrowserStream.js.map +1 -1
- package/dist/BrowserWorker.d.ts +25 -4
- package/dist/BrowserWorker.d.ts.map +1 -1
- package/dist/BrowserWorker.js +34 -6
- package/dist/BrowserWorker.js.map +1 -1
- package/dist/BrowserWorkerRunner.d.ts +31 -6
- package/dist/BrowserWorkerRunner.d.ts.map +1 -1
- package/dist/BrowserWorkerRunner.js +43 -10
- package/dist/BrowserWorkerRunner.js.map +1 -1
- package/dist/Clipboard.d.ts +59 -14
- package/dist/Clipboard.d.ts.map +1 -1
- package/dist/Clipboard.js +34 -12
- package/dist/Clipboard.js.map +1 -1
- package/dist/Geolocation.d.ts +80 -25
- package/dist/Geolocation.d.ts.map +1 -1
- package/dist/Geolocation.js +48 -17
- package/dist/Geolocation.js.map +1 -1
- package/dist/IndexedDb.d.ts +75 -0
- package/dist/IndexedDb.d.ts.map +1 -0
- package/dist/IndexedDb.js +85 -0
- package/dist/IndexedDb.js.map +1 -0
- package/dist/IndexedDbDatabase.d.ts +153 -0
- package/dist/IndexedDbDatabase.d.ts.map +1 -0
- package/dist/IndexedDbDatabase.js +327 -0
- package/dist/IndexedDbDatabase.js.map +1 -0
- package/dist/IndexedDbQueryBuilder.d.ts +422 -0
- package/dist/IndexedDbQueryBuilder.d.ts.map +1 -0
- package/dist/IndexedDbQueryBuilder.js +935 -0
- package/dist/IndexedDbQueryBuilder.js.map +1 -0
- package/dist/IndexedDbTable.d.ts +166 -0
- package/dist/IndexedDbTable.d.ts.map +1 -0
- package/dist/IndexedDbTable.js +71 -0
- package/dist/IndexedDbTable.js.map +1 -0
- package/dist/IndexedDbVersion.d.ts +99 -0
- package/dist/IndexedDbVersion.d.ts.map +1 -0
- package/dist/IndexedDbVersion.js +44 -0
- package/dist/IndexedDbVersion.js.map +1 -0
- package/dist/Permissions.d.ts +52 -16
- package/dist/Permissions.d.ts.map +1 -1
- package/dist/Permissions.js +42 -11
- package/dist/Permissions.js.map +1 -1
- package/dist/index.d.ts +38 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -10
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/BrowserCrypto.ts +97 -0
- package/src/BrowserHttpClient.ts +72 -27
- package/src/BrowserKeyValueStore.ts +160 -12
- package/src/BrowserPersistence.ts +334 -0
- package/src/BrowserRuntime.ts +64 -4
- package/src/BrowserSocket.ts +33 -6
- package/src/BrowserStream.ts +19 -7
- package/src/BrowserWorker.ts +34 -6
- package/src/BrowserWorkerRunner.ts +43 -10
- package/src/Clipboard.ts +56 -14
- package/src/Geolocation.ts +76 -21
- package/src/IndexedDb.ts +113 -0
- package/src/IndexedDbDatabase.ts +648 -0
- package/src/IndexedDbQueryBuilder.ts +2032 -0
- package/src/IndexedDbTable.ts +260 -0
- package/src/IndexedDbVersion.ts +138 -0
- package/src/Permissions.ts +47 -14
- 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
|
+
}
|