@effect/platform-browser 4.0.0-beta.8 → 4.0.0-beta.81
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,2032 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds effectful, schema-aware queries for typed browser IndexedDB versions.
|
|
3
|
+
*
|
|
4
|
+
* An `IndexedDbQueryBuilder` is created from an open database and a version's
|
|
5
|
+
* table descriptors, then exposes `from(tableName)` as the entry point for
|
|
6
|
+
* table operations. Query objects can select, count, delete, insert, upsert,
|
|
7
|
+
* clear tables, stream paged reads, react to invalidations, and run multiple
|
|
8
|
+
* effects in a shared `IDBTransaction` with `withTransaction`. Reads decode
|
|
9
|
+
* stored rows with the table schema, and writes encode input values before
|
|
10
|
+
* sending them to IndexedDB.
|
|
11
|
+
*
|
|
12
|
+
* @since 4.0.0
|
|
13
|
+
*/
|
|
14
|
+
import type { NonEmptyReadonlyArray } from "effect/Array"
|
|
15
|
+
import * as Cause from "effect/Cause"
|
|
16
|
+
import * as Context from "effect/Context"
|
|
17
|
+
import * as Data from "effect/Data"
|
|
18
|
+
import * as Effect from "effect/Effect"
|
|
19
|
+
import * as Effectable from "effect/Effectable"
|
|
20
|
+
import * as Fiber from "effect/Fiber"
|
|
21
|
+
import type { Inspectable } from "effect/Inspectable"
|
|
22
|
+
import { BaseProto } from "effect/Inspectable"
|
|
23
|
+
import type * as MutableRef from "effect/MutableRef"
|
|
24
|
+
import * as Option from "effect/Option"
|
|
25
|
+
import * as Pipeable from "effect/Pipeable"
|
|
26
|
+
import type * as Queue from "effect/Queue"
|
|
27
|
+
import type * as Record from "effect/Record"
|
|
28
|
+
import * as References from "effect/References"
|
|
29
|
+
import * as Schema from "effect/Schema"
|
|
30
|
+
import * as SchemaIssue from "effect/SchemaIssue"
|
|
31
|
+
import * as SchemaParser from "effect/SchemaParser"
|
|
32
|
+
import type * as Scope from "effect/Scope"
|
|
33
|
+
import * as Stream from "effect/Stream"
|
|
34
|
+
import type * as Reactivity from "effect/unstable/reactivity/Reactivity"
|
|
35
|
+
import * as Utils from "effect/Utils"
|
|
36
|
+
import type * as IndexedDb from "./IndexedDb.ts"
|
|
37
|
+
import type * as IndexedDbDatabase from "./IndexedDbDatabase.ts"
|
|
38
|
+
import type * as IndexedDbTable from "./IndexedDbTable.ts"
|
|
39
|
+
import type * as IndexedDbVersion from "./IndexedDbVersion.ts"
|
|
40
|
+
|
|
41
|
+
const ErrorTypeId = "~@effect/platform-browser/IndexedDbQueryBuilder/IndexedDbQueryError"
|
|
42
|
+
|
|
43
|
+
const CommonProto = {
|
|
44
|
+
[Symbol.iterator]() {
|
|
45
|
+
return new Utils.SingleShotGen(this) as any
|
|
46
|
+
},
|
|
47
|
+
...Pipeable.Prototype,
|
|
48
|
+
...BaseProto,
|
|
49
|
+
toJSON(this: any) {
|
|
50
|
+
return {
|
|
51
|
+
_id: "IndexedDbQueryBuilder"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* String union describing IndexedDB query failure categories such as decoding, encoding, and transaction errors.
|
|
58
|
+
*
|
|
59
|
+
* @category errors
|
|
60
|
+
* @since 4.0.0
|
|
61
|
+
*/
|
|
62
|
+
export type ErrorReason =
|
|
63
|
+
| "UnknownError"
|
|
64
|
+
| "DecodeError"
|
|
65
|
+
| "EncodeError"
|
|
66
|
+
| "TransactionError"
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Tagged error for IndexedDB query operations, carrying a query error reason and the original cause.
|
|
70
|
+
*
|
|
71
|
+
* **Details**
|
|
72
|
+
*
|
|
73
|
+
* `reason` is the query failure category, `cause` preserves the underlying
|
|
74
|
+
* schema, IndexedDB request, transaction, or user callback failure, and
|
|
75
|
+
* `message` is set to the reason.
|
|
76
|
+
*
|
|
77
|
+
* @see {@link ErrorReason} for the supported failure categories
|
|
78
|
+
*
|
|
79
|
+
* @category errors
|
|
80
|
+
* @since 4.0.0
|
|
81
|
+
*/
|
|
82
|
+
export class IndexedDbQueryError extends Data.TaggedError(
|
|
83
|
+
"IndexedDbQueryError"
|
|
84
|
+
)<{
|
|
85
|
+
reason: ErrorReason
|
|
86
|
+
cause: unknown
|
|
87
|
+
}> {
|
|
88
|
+
/**
|
|
89
|
+
* Marks this value as an IndexedDB query builder error for runtime guards.
|
|
90
|
+
*
|
|
91
|
+
* @since 4.0.0
|
|
92
|
+
*/
|
|
93
|
+
readonly [ErrorTypeId]: typeof ErrorTypeId = ErrorTypeId
|
|
94
|
+
|
|
95
|
+
override readonly message = this.reason
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Typed query builder for an IndexedDB version, with helpers for table queries, database access, clearing data, and running effects in a shared transaction.
|
|
100
|
+
*
|
|
101
|
+
* @category models
|
|
102
|
+
* @since 4.0.0
|
|
103
|
+
*/
|
|
104
|
+
export interface IndexedDbQueryBuilder<
|
|
105
|
+
Source extends IndexedDbVersion.AnyWithProps
|
|
106
|
+
> extends Pipeable.Pipeable, Inspectable {
|
|
107
|
+
readonly tables: ReadonlyMap<string, IndexedDbVersion.Tables<Source>>
|
|
108
|
+
readonly database: MutableRef.MutableRef<globalThis.IDBDatabase>
|
|
109
|
+
readonly reactivity: Reactivity.Reactivity["Service"]
|
|
110
|
+
readonly IDBKeyRange: typeof globalThis.IDBKeyRange
|
|
111
|
+
readonly IDBTransaction: globalThis.IDBTransaction | undefined
|
|
112
|
+
|
|
113
|
+
readonly use: <A = unknown>(
|
|
114
|
+
f: (database: globalThis.IDBDatabase) => A
|
|
115
|
+
) => Effect.Effect<A, IndexedDbQueryError>
|
|
116
|
+
|
|
117
|
+
readonly from: <
|
|
118
|
+
const Name extends IndexedDbTable.TableName<
|
|
119
|
+
IndexedDbVersion.Tables<Source>
|
|
120
|
+
>
|
|
121
|
+
>(
|
|
122
|
+
table: Name
|
|
123
|
+
) => IndexedDbQuery.From<IndexedDbVersion.TableWithName<Source, Name>>
|
|
124
|
+
|
|
125
|
+
/** @internal */
|
|
126
|
+
readonly fromCache: Map<string, IndexedDbQuery.From<IndexedDbVersion.TableWithName<Source, any>>>
|
|
127
|
+
|
|
128
|
+
readonly clearAll: Effect.Effect<void, IndexedDbQueryError>
|
|
129
|
+
|
|
130
|
+
readonly withTransaction: <
|
|
131
|
+
Tables extends NonEmptyReadonlyArray<
|
|
132
|
+
IndexedDbTable.TableName<IndexedDbVersion.Tables<Source>>
|
|
133
|
+
>,
|
|
134
|
+
Mode extends "readonly" | "readwrite"
|
|
135
|
+
>(options: {
|
|
136
|
+
readonly tables: Tables
|
|
137
|
+
readonly mode: Mode
|
|
138
|
+
readonly durability?: IDBTransactionDurability
|
|
139
|
+
}) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, IndexedDbTransaction>>
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Valid key-path type for a table schema, using encoded fields whose values are IndexedDB-valid keys.
|
|
144
|
+
*
|
|
145
|
+
* @category models
|
|
146
|
+
* @since 4.0.0
|
|
147
|
+
*/
|
|
148
|
+
export type KeyPath<TableSchema extends IndexedDbTable.AnySchemaStruct> =
|
|
149
|
+
| IndexedDbValidKeys<TableSchema>
|
|
150
|
+
| NonEmptyReadonlyArray<IndexedDbValidKeys<TableSchema>>
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Valid numeric key-path type for a table schema, used for auto-increment key paths.
|
|
154
|
+
*
|
|
155
|
+
* @category models
|
|
156
|
+
* @since 4.0.0
|
|
157
|
+
*/
|
|
158
|
+
export type KeyPathNumber<TableSchema extends IndexedDbTable.AnySchemaStruct> =
|
|
159
|
+
| IndexedDbValidNumberKeys<TableSchema>
|
|
160
|
+
| NonEmptyReadonlyArray<IndexedDbValidNumberKeys<TableSchema>>
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Namespace containing the typed IndexedDB query model interfaces and helper types.
|
|
164
|
+
*
|
|
165
|
+
* @since 4.0.0
|
|
166
|
+
*/
|
|
167
|
+
export declare namespace IndexedDbQuery {
|
|
168
|
+
/**
|
|
169
|
+
* Decoded row type returned by select queries, adding a `key` field when the table does not define a key path.
|
|
170
|
+
*
|
|
171
|
+
* @category models
|
|
172
|
+
* @since 4.0.0
|
|
173
|
+
*/
|
|
174
|
+
export type SelectType<
|
|
175
|
+
Table extends IndexedDbTable.AnyWithProps
|
|
176
|
+
> = [IndexedDbTable.KeyPath<Table>] extends [undefined] ? IndexedDbTable.TableSchema<Table>["Type"] & {
|
|
177
|
+
readonly key: (typeof IndexedDb.IDBValidKey)["Type"]
|
|
178
|
+
} :
|
|
179
|
+
IndexedDbTable.TableSchema<Table>["Type"]
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Input type for insert and upsert operations, adjusted for auto-increment keys and out-of-line keys.
|
|
183
|
+
*
|
|
184
|
+
* @category models
|
|
185
|
+
* @since 4.0.0
|
|
186
|
+
*/
|
|
187
|
+
export type ModifyType<
|
|
188
|
+
Table extends IndexedDbTable.AnyWithProps
|
|
189
|
+
> =
|
|
190
|
+
& (IndexedDbTable.AutoIncrement<Table> extends true ?
|
|
191
|
+
& {
|
|
192
|
+
[
|
|
193
|
+
key in keyof Schema.Struct.MakeIn<
|
|
194
|
+
Omit<
|
|
195
|
+
IndexedDbTable.TableSchema<Table>["fields"],
|
|
196
|
+
IndexedDbTable.KeyPath<Table>
|
|
197
|
+
>
|
|
198
|
+
>
|
|
199
|
+
]: key extends keyof Schema.Struct.MakeIn<
|
|
200
|
+
IndexedDbTable.TableSchema<Table>["fields"]
|
|
201
|
+
> ? Schema.Struct.MakeIn<
|
|
202
|
+
IndexedDbTable.TableSchema<Table>["fields"]
|
|
203
|
+
>[key]
|
|
204
|
+
: never
|
|
205
|
+
}
|
|
206
|
+
& {
|
|
207
|
+
[key in IndexedDbTable.KeyPath<Table>]?: number | undefined
|
|
208
|
+
}
|
|
209
|
+
: Schema.Struct.MakeIn<IndexedDbTable.TableSchema<Table>["fields"]>)
|
|
210
|
+
& ([IndexedDbTable.KeyPath<Table>] extends [undefined] ? {
|
|
211
|
+
key: IDBValidKey
|
|
212
|
+
}
|
|
213
|
+
: {})
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Value type accepted by `equals` comparisons for a table key path or index.
|
|
217
|
+
*
|
|
218
|
+
* @category models
|
|
219
|
+
* @since 4.0.0
|
|
220
|
+
*/
|
|
221
|
+
export type EqualsType<
|
|
222
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
223
|
+
Index extends keyof Table["indexes"],
|
|
224
|
+
KeyPath = [Index] extends [never] ? Table["keyPath"] : Table["indexes"][Index],
|
|
225
|
+
Type = Table["tableSchema"]["Encoded"]
|
|
226
|
+
> = KeyPath extends keyof Type ? Type[KeyPath]
|
|
227
|
+
: { [I in keyof KeyPath]: KeyPath[I] extends keyof Type ? Type[KeyPath[I]] | [] : never }
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Value type accepted by range comparisons for a table key path or index, including partial tuples for compound indexes.
|
|
231
|
+
*
|
|
232
|
+
* @category models
|
|
233
|
+
* @since 4.0.0
|
|
234
|
+
*/
|
|
235
|
+
export type ExtractIndexType<
|
|
236
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
237
|
+
Index extends keyof Table["indexes"],
|
|
238
|
+
KeyPath = [Index] extends [never] ? Table["keyPath"] : Table["indexes"][Index],
|
|
239
|
+
Type = Table["tableSchema"]["Encoded"]
|
|
240
|
+
> = KeyPath extends keyof Type ? Type[KeyPath]
|
|
241
|
+
: KeyPath extends readonly [infer K, ...infer Rest] ? K extends keyof Type ? [
|
|
242
|
+
Type[K],
|
|
243
|
+
...{ [P in keyof Rest]?: Rest[P] extends keyof Type ? Type[Rest[P]] | [] : never }
|
|
244
|
+
] :
|
|
245
|
+
never :
|
|
246
|
+
never
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Mutation input type for insert and upsert operations, including any required key fields.
|
|
250
|
+
*
|
|
251
|
+
* @category models
|
|
252
|
+
* @since 4.0.0
|
|
253
|
+
*/
|
|
254
|
+
export type ModifyWithKey<Table extends IndexedDbTable.AnyWithProps> = ModifyType<Table>
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Query entry point for a table, exposing clear, select, count, delete, insert, and upsert operations.
|
|
258
|
+
*
|
|
259
|
+
* @category models
|
|
260
|
+
* @since 4.0.0
|
|
261
|
+
*/
|
|
262
|
+
export interface From<Table extends IndexedDbTable.AnyWithProps> {
|
|
263
|
+
readonly table: Table
|
|
264
|
+
readonly database: MutableRef.MutableRef<globalThis.IDBDatabase>
|
|
265
|
+
readonly IDBKeyRange: typeof globalThis.IDBKeyRange
|
|
266
|
+
readonly reactivity: Reactivity.Reactivity["Service"]
|
|
267
|
+
|
|
268
|
+
readonly clear: Effect.Effect<void, IndexedDbQueryError>
|
|
269
|
+
|
|
270
|
+
readonly select: {
|
|
271
|
+
<Index extends IndexedDbDatabase.IndexFromTable<Table>>(
|
|
272
|
+
index: Index
|
|
273
|
+
): Select<Table, Index>
|
|
274
|
+
(): Select<Table, never>
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** @internal */
|
|
278
|
+
readonly selectCache: Map<
|
|
279
|
+
string | undefined,
|
|
280
|
+
IndexedDbQuery.Select<any, never>
|
|
281
|
+
>
|
|
282
|
+
|
|
283
|
+
readonly count: {
|
|
284
|
+
<Index extends IndexedDbDatabase.IndexFromTable<Table>>(
|
|
285
|
+
index: Index
|
|
286
|
+
): Count<Table, Index>
|
|
287
|
+
(): Count<Table, never>
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** @internal */
|
|
291
|
+
readonly countCache: Map<
|
|
292
|
+
string | undefined,
|
|
293
|
+
IndexedDbQuery.Count<any, never>
|
|
294
|
+
>
|
|
295
|
+
|
|
296
|
+
readonly delete: {
|
|
297
|
+
<Index extends IndexedDbDatabase.IndexFromTable<Table>>(
|
|
298
|
+
index: Index
|
|
299
|
+
): DeletePartial<Table, Index>
|
|
300
|
+
(): DeletePartial<Table, never>
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** @internal */
|
|
304
|
+
readonly deleteCache: Map<
|
|
305
|
+
string | undefined,
|
|
306
|
+
IndexedDbQuery.DeletePartial<any, never>
|
|
307
|
+
>
|
|
308
|
+
|
|
309
|
+
readonly insert: (value: ModifyWithKey<Table>) => Modify<Table>
|
|
310
|
+
readonly insertAll: (
|
|
311
|
+
values: Array<ModifyWithKey<Table>>
|
|
312
|
+
) => ModifyAll<Table>
|
|
313
|
+
readonly upsert: (value: ModifyWithKey<Table>) => Modify<Table>
|
|
314
|
+
readonly upsertAll: (
|
|
315
|
+
values: Array<ModifyWithKey<Table>>
|
|
316
|
+
) => ModifyAll<Table>
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Effect model for clearing all rows from a table.
|
|
321
|
+
*
|
|
322
|
+
* @category models
|
|
323
|
+
* @since 4.0.0
|
|
324
|
+
*/
|
|
325
|
+
export interface Clear<
|
|
326
|
+
Table extends IndexedDbTable.AnyWithProps
|
|
327
|
+
> extends Effect.Effect<void, IndexedDbQueryError> {
|
|
328
|
+
readonly from: From<Table>
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
type ComparisonKeys = "equals" | "gte" | "lte" | "gt" | "lt" | "between"
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Effect model for counting table rows, optionally constrained by an index and key-range comparisons.
|
|
335
|
+
*
|
|
336
|
+
* @category models
|
|
337
|
+
* @since 4.0.0
|
|
338
|
+
*/
|
|
339
|
+
export interface Count<
|
|
340
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
341
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>
|
|
342
|
+
> extends Effect.Effect<number, IndexedDbQueryError> {
|
|
343
|
+
readonly from: From<Table>
|
|
344
|
+
readonly index?: Index
|
|
345
|
+
readonly only?: ExtractIndexType<Table, Index>
|
|
346
|
+
readonly lowerBound?: ExtractIndexType<Table, Index>
|
|
347
|
+
readonly upperBound?: ExtractIndexType<Table, Index>
|
|
348
|
+
readonly excludeLowerBound?: boolean
|
|
349
|
+
readonly excludeUpperBound?: boolean
|
|
350
|
+
|
|
351
|
+
readonly equals: (
|
|
352
|
+
value: EqualsType<Table, Index>
|
|
353
|
+
) => Omit<Count<Table, Index>, ComparisonKeys>
|
|
354
|
+
|
|
355
|
+
readonly gte: (
|
|
356
|
+
value: ExtractIndexType<Table, Index>
|
|
357
|
+
) => Omit<Count<Table, Index>, ComparisonKeys>
|
|
358
|
+
|
|
359
|
+
readonly lte: (
|
|
360
|
+
value: ExtractIndexType<Table, Index>
|
|
361
|
+
) => Omit<Count<Table, Index>, ComparisonKeys>
|
|
362
|
+
|
|
363
|
+
readonly gt: (
|
|
364
|
+
value: ExtractIndexType<Table, Index>
|
|
365
|
+
) => Omit<Count<Table, Index>, ComparisonKeys>
|
|
366
|
+
|
|
367
|
+
readonly lt: (
|
|
368
|
+
value: ExtractIndexType<Table, Index>
|
|
369
|
+
) => Omit<Count<Table, Index>, ComparisonKeys>
|
|
370
|
+
|
|
371
|
+
readonly between: (
|
|
372
|
+
lowerBound: ExtractIndexType<Table, Index>,
|
|
373
|
+
upperBound: ExtractIndexType<Table, Index>,
|
|
374
|
+
options?: { excludeLowerBound?: boolean; excludeUpperBound?: boolean }
|
|
375
|
+
) => Omit<Count<Table, Index>, ComparisonKeys>
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Intermediate delete builder used to choose a key range or limit before producing an executable delete query.
|
|
380
|
+
*
|
|
381
|
+
* @category models
|
|
382
|
+
* @since 4.0.0
|
|
383
|
+
*/
|
|
384
|
+
export interface DeletePartial<
|
|
385
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
386
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>
|
|
387
|
+
> {
|
|
388
|
+
readonly from: From<Table>
|
|
389
|
+
readonly index?: Index
|
|
390
|
+
|
|
391
|
+
readonly equals: (
|
|
392
|
+
value: EqualsType<Table, Index>
|
|
393
|
+
) => Delete<Table, Index>
|
|
394
|
+
|
|
395
|
+
readonly gte: (
|
|
396
|
+
value: ExtractIndexType<Table, Index>
|
|
397
|
+
) => Delete<Table, Index>
|
|
398
|
+
|
|
399
|
+
readonly lte: (
|
|
400
|
+
value: ExtractIndexType<Table, Index>
|
|
401
|
+
) => Delete<Table, Index>
|
|
402
|
+
|
|
403
|
+
readonly gt: (
|
|
404
|
+
value: ExtractIndexType<Table, Index>
|
|
405
|
+
) => Delete<Table, Index>
|
|
406
|
+
|
|
407
|
+
readonly lt: (
|
|
408
|
+
value: ExtractIndexType<Table, Index>
|
|
409
|
+
) => Delete<Table, Index>
|
|
410
|
+
|
|
411
|
+
readonly between: (
|
|
412
|
+
lowerBound: ExtractIndexType<Table, Index>,
|
|
413
|
+
upperBound: ExtractIndexType<Table, Index>,
|
|
414
|
+
options?: { excludeLowerBound?: boolean; excludeUpperBound?: boolean }
|
|
415
|
+
) => Delete<Table, Index>
|
|
416
|
+
|
|
417
|
+
readonly limit: (
|
|
418
|
+
limit: number
|
|
419
|
+
) => DeleteWithout<Table, Index, "limit">
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
type DeleteWithout<
|
|
423
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
424
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>,
|
|
425
|
+
ExcludedKeys extends string
|
|
426
|
+
> = Omit<Delete<Table, Index, ExcludedKeys>, ExcludedKeys>
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Effect model for deleting rows from a table, with optional key-range, limit, filter, and reactivity invalidation helpers.
|
|
430
|
+
*
|
|
431
|
+
* @category models
|
|
432
|
+
* @since 4.0.0
|
|
433
|
+
*/
|
|
434
|
+
export interface Delete<
|
|
435
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
436
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>,
|
|
437
|
+
ExcludedKeys extends string = never
|
|
438
|
+
> extends Effect.Effect<void, IndexedDbQueryError> {
|
|
439
|
+
readonly delete: DeletePartial<Table, Index>
|
|
440
|
+
readonly index?: Index
|
|
441
|
+
readonly limitValue?: number
|
|
442
|
+
readonly only?: ExtractIndexType<Table, Index>
|
|
443
|
+
readonly lowerBound?: ExtractIndexType<Table, Index>
|
|
444
|
+
readonly upperBound?: ExtractIndexType<Table, Index>
|
|
445
|
+
readonly excludeLowerBound?: boolean
|
|
446
|
+
readonly excludeUpperBound?: boolean
|
|
447
|
+
readonly predicate?: (item: IndexedDbTable.Encoded<Table>) => boolean
|
|
448
|
+
|
|
449
|
+
readonly limit: (
|
|
450
|
+
limit: number
|
|
451
|
+
) => DeleteWithout<Table, Index, ExcludedKeys | "limit">
|
|
452
|
+
|
|
453
|
+
readonly filter: (
|
|
454
|
+
f: (value: IndexedDbTable.Encoded<Table>) => boolean
|
|
455
|
+
) => DeleteWithout<Table, Index, ExcludedKeys>
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Invalidate any queries using Reactivity service with the provided keys.
|
|
459
|
+
*
|
|
460
|
+
* **Details**
|
|
461
|
+
*
|
|
462
|
+
* If no keys are provided, the table name is used as the reactivity key.
|
|
463
|
+
*/
|
|
464
|
+
readonly invalidate: (
|
|
465
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
466
|
+
) => Effect.Effect<void, IndexedDbQueryError, IndexedDbTable.Context<Table>>
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
type SelectWithout<
|
|
470
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
471
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>,
|
|
472
|
+
ExcludedKeys extends string
|
|
473
|
+
> = Omit<Select<Table, Index, ExcludedKeys>, ExcludedKeys>
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Effect model for selecting rows from a table, with chainable range, paging, filtering, streaming, and reactive query helpers.
|
|
477
|
+
*
|
|
478
|
+
* @category models
|
|
479
|
+
* @since 4.0.0
|
|
480
|
+
*/
|
|
481
|
+
export interface Select<
|
|
482
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
483
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>,
|
|
484
|
+
ExcludedKeys extends string = never
|
|
485
|
+
> extends
|
|
486
|
+
Effect.Effect<
|
|
487
|
+
Array<SelectType<Table>>,
|
|
488
|
+
IndexedDbQueryError,
|
|
489
|
+
IndexedDbTable.Context<Table>
|
|
490
|
+
>
|
|
491
|
+
{
|
|
492
|
+
readonly from: From<Table>
|
|
493
|
+
readonly index?: Index
|
|
494
|
+
readonly limitValue?: number
|
|
495
|
+
readonly offsetValue?: number
|
|
496
|
+
readonly reverseValue?: boolean
|
|
497
|
+
readonly only?: ExtractIndexType<Table, Index>
|
|
498
|
+
readonly lowerBound?: ExtractIndexType<Table, Index>
|
|
499
|
+
readonly upperBound?: ExtractIndexType<Table, Index>
|
|
500
|
+
readonly excludeLowerBound?: boolean
|
|
501
|
+
readonly excludeUpperBound?: boolean
|
|
502
|
+
readonly predicate?: (item: IndexedDbTable.Encoded<Table>) => boolean
|
|
503
|
+
|
|
504
|
+
readonly equals: (
|
|
505
|
+
value: EqualsType<Table, Index>
|
|
506
|
+
) => SelectWithout<Table, Index, ExcludedKeys | ComparisonKeys>
|
|
507
|
+
|
|
508
|
+
readonly gte: (
|
|
509
|
+
value: ExtractIndexType<Table, Index>
|
|
510
|
+
) => SelectWithout<Table, Index, ExcludedKeys | ComparisonKeys>
|
|
511
|
+
|
|
512
|
+
readonly lte: (
|
|
513
|
+
value: ExtractIndexType<Table, Index>
|
|
514
|
+
) => SelectWithout<Table, Index, ExcludedKeys | ComparisonKeys>
|
|
515
|
+
|
|
516
|
+
readonly gt: (
|
|
517
|
+
value: ExtractIndexType<Table, Index>
|
|
518
|
+
) => SelectWithout<Table, Index, ExcludedKeys | ComparisonKeys>
|
|
519
|
+
|
|
520
|
+
readonly lt: (
|
|
521
|
+
value: ExtractIndexType<Table, Index>
|
|
522
|
+
) => SelectWithout<Table, Index, ExcludedKeys | ComparisonKeys>
|
|
523
|
+
|
|
524
|
+
readonly between: (
|
|
525
|
+
lowerBound: ExtractIndexType<Table, Index>,
|
|
526
|
+
upperBound: ExtractIndexType<Table, Index>,
|
|
527
|
+
options?: { excludeLowerBound?: boolean; excludeUpperBound?: boolean }
|
|
528
|
+
) => SelectWithout<Table, Index, ExcludedKeys | ComparisonKeys>
|
|
529
|
+
|
|
530
|
+
readonly limit: (
|
|
531
|
+
limit: number
|
|
532
|
+
) => SelectWithout<Table, Index, ExcludedKeys | "limit" | "first">
|
|
533
|
+
|
|
534
|
+
readonly offset: (
|
|
535
|
+
offset: number
|
|
536
|
+
) => SelectWithout<Table, Index, ExcludedKeys | "offset" | "first">
|
|
537
|
+
|
|
538
|
+
readonly reverse: () => SelectWithout<Table, Index, ExcludedKeys | "reverse" | "first">
|
|
539
|
+
|
|
540
|
+
readonly filter: (
|
|
541
|
+
f: (value: IndexedDbTable.Encoded<Table>) => boolean
|
|
542
|
+
) => SelectWithout<Table, Index, ExcludedKeys | "first">
|
|
543
|
+
|
|
544
|
+
readonly first: () => First<Table, Index>
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Stream the selected data.
|
|
548
|
+
*
|
|
549
|
+
* **Details**
|
|
550
|
+
*
|
|
551
|
+
* The default chunk size is 100.
|
|
552
|
+
*/
|
|
553
|
+
readonly stream: (options?: {
|
|
554
|
+
readonly chunkSize?: number | undefined
|
|
555
|
+
}) => Stream.Stream<
|
|
556
|
+
SelectType<Table>,
|
|
557
|
+
IndexedDbQueryError,
|
|
558
|
+
IndexedDbTable.Context<Table>
|
|
559
|
+
>
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Use the Reactivity service to react to changes to the selected data.
|
|
563
|
+
*
|
|
564
|
+
* **Details**
|
|
565
|
+
*
|
|
566
|
+
* By default, the table name is used as the reactivity key.
|
|
567
|
+
*/
|
|
568
|
+
readonly reactive: (
|
|
569
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
570
|
+
) => Stream.Stream<
|
|
571
|
+
Array<SelectType<Table>>,
|
|
572
|
+
IndexedDbQueryError,
|
|
573
|
+
IndexedDbTable.Context<Table>
|
|
574
|
+
>
|
|
575
|
+
readonly reactiveQueue: (
|
|
576
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
577
|
+
) => Effect.Effect<
|
|
578
|
+
Queue.Dequeue<Array<SelectType<Table>>, IndexedDbQueryError>,
|
|
579
|
+
never,
|
|
580
|
+
Scope.Scope | IndexedDbTable.Context<Table>
|
|
581
|
+
>
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Effect model for selecting the first matching row, failing with `NoSuchElementError` when no row is found.
|
|
586
|
+
*
|
|
587
|
+
* @category models
|
|
588
|
+
* @since 4.0.0
|
|
589
|
+
*/
|
|
590
|
+
export interface First<
|
|
591
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
592
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>
|
|
593
|
+
> extends
|
|
594
|
+
Effect.Effect<
|
|
595
|
+
SelectType<Table>,
|
|
596
|
+
IndexedDbQueryError | Cause.NoSuchElementError,
|
|
597
|
+
IndexedDbTable.Context<Table>
|
|
598
|
+
>
|
|
599
|
+
{
|
|
600
|
+
readonly select: Select<Table, Index>
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Use the Reactivity service to react to changes to the selected data.
|
|
604
|
+
*
|
|
605
|
+
* **Details**
|
|
606
|
+
*
|
|
607
|
+
* By default, the table name is used as the reactivity key.
|
|
608
|
+
*/
|
|
609
|
+
readonly reactive: (
|
|
610
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
611
|
+
) => Stream.Stream<
|
|
612
|
+
SelectType<Table>,
|
|
613
|
+
IndexedDbQueryError | Cause.NoSuchElementError,
|
|
614
|
+
IndexedDbTable.Context<Table>
|
|
615
|
+
>
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Use the Reactivity service to react to changes to the selected data.
|
|
619
|
+
*
|
|
620
|
+
* **Details**
|
|
621
|
+
*
|
|
622
|
+
* By default, the table name is used as the reactivity key.
|
|
623
|
+
*/
|
|
624
|
+
readonly reactiveQueue: (
|
|
625
|
+
keys: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>>
|
|
626
|
+
) => Effect.Effect<
|
|
627
|
+
Queue.Dequeue<SelectType<Table>, IndexedDbQueryError | Cause.NoSuchElementError>,
|
|
628
|
+
never,
|
|
629
|
+
Scope.Scope | IndexedDbTable.Context<Table>
|
|
630
|
+
>
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Effect model for a select query filtered by one or more predicates over encoded table rows.
|
|
635
|
+
*
|
|
636
|
+
* @category models
|
|
637
|
+
* @since 4.0.0
|
|
638
|
+
*/
|
|
639
|
+
export interface Filter<
|
|
640
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
641
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>
|
|
642
|
+
> extends
|
|
643
|
+
Effect.Effect<
|
|
644
|
+
Array<SelectType<Table>>,
|
|
645
|
+
IndexedDbQueryError,
|
|
646
|
+
IndexedDbTable.Context<Table>
|
|
647
|
+
>
|
|
648
|
+
{
|
|
649
|
+
readonly select: Select<Table, Index>
|
|
650
|
+
readonly predicate: (item: IndexedDbTable.Encoded<Table>) => boolean
|
|
651
|
+
readonly filter: (
|
|
652
|
+
f: (value: IndexedDbTable.Encoded<Table>) => boolean
|
|
653
|
+
) => Filter<Table, Index>
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Effect model for inserting or upserting one row, returning the resulting IndexedDB key and supporting reactivity invalidation.
|
|
658
|
+
*
|
|
659
|
+
* @category models
|
|
660
|
+
* @since 4.0.0
|
|
661
|
+
*/
|
|
662
|
+
export interface Modify<
|
|
663
|
+
Table extends IndexedDbTable.AnyWithProps
|
|
664
|
+
> extends
|
|
665
|
+
Effect.Effect<
|
|
666
|
+
globalThis.IDBValidKey,
|
|
667
|
+
IndexedDbQueryError,
|
|
668
|
+
IndexedDbTable.Context<Table>
|
|
669
|
+
>
|
|
670
|
+
{
|
|
671
|
+
readonly operation: "add" | "put"
|
|
672
|
+
readonly from: From<Table>
|
|
673
|
+
readonly value: ModifyWithKey<Table>
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Invalidate any queries using Reactivity service with the provided keys.
|
|
677
|
+
*
|
|
678
|
+
* **Details**
|
|
679
|
+
*
|
|
680
|
+
* If no keys are provided, the table name is used as the reactivity key.
|
|
681
|
+
*/
|
|
682
|
+
readonly invalidate: (
|
|
683
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
684
|
+
) => Effect.Effect<globalThis.IDBValidKey, IndexedDbQueryError, IndexedDbTable.Context<Table>>
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Effect model for inserting or upserting multiple rows, returning the resulting IndexedDB keys and supporting reactivity invalidation.
|
|
689
|
+
*
|
|
690
|
+
* @category models
|
|
691
|
+
* @since 4.0.0
|
|
692
|
+
*/
|
|
693
|
+
export interface ModifyAll<
|
|
694
|
+
Table extends IndexedDbTable.AnyWithProps
|
|
695
|
+
> extends
|
|
696
|
+
Effect.Effect<
|
|
697
|
+
Array<globalThis.IDBValidKey>,
|
|
698
|
+
IndexedDbQueryError,
|
|
699
|
+
IndexedDbTable.Context<Table>
|
|
700
|
+
>
|
|
701
|
+
{
|
|
702
|
+
readonly operation: "add" | "put"
|
|
703
|
+
readonly from: From<Table>
|
|
704
|
+
readonly values: Array<ModifyWithKey<Table>>
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Invalidate any queries using Reactivity service with the provided keys.
|
|
708
|
+
*
|
|
709
|
+
* **Details**
|
|
710
|
+
*
|
|
711
|
+
* If no keys are provided, the table name is used as the reactivity key.
|
|
712
|
+
*/
|
|
713
|
+
readonly invalidate: (
|
|
714
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
715
|
+
) => Effect.Effect<globalThis.IDBValidKey, IndexedDbQueryError, IndexedDbTable.Context<Table>>
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Service tag for the active `IDBTransaction` used to share a transaction across IndexedDB query effects.
|
|
721
|
+
*
|
|
722
|
+
* @category models
|
|
723
|
+
* @since 4.0.0
|
|
724
|
+
*/
|
|
725
|
+
export class IndexedDbTransaction extends Context.Service<IndexedDbTransaction, globalThis.IDBTransaction>()(
|
|
726
|
+
"@effect/platform-browser/IndexedDbQueryBuilder/IndexedDbTransaction"
|
|
727
|
+
) {}
|
|
728
|
+
|
|
729
|
+
// -----------------------------------------------------------------------------
|
|
730
|
+
// internal
|
|
731
|
+
// -----------------------------------------------------------------------------
|
|
732
|
+
|
|
733
|
+
type IndexedDbValidKeys<TableSchema extends IndexedDbTable.AnySchemaStruct> = keyof TableSchema["Encoded"] extends
|
|
734
|
+
infer K ? K extends keyof TableSchema["Encoded"] ? TableSchema["Encoded"][K] extends Readonly<IDBValidKey> ? K
|
|
735
|
+
: never
|
|
736
|
+
: never
|
|
737
|
+
: never
|
|
738
|
+
|
|
739
|
+
type IndexedDbValidNumberKeys<
|
|
740
|
+
TableSchema extends IndexedDbTable.AnySchemaStruct
|
|
741
|
+
> = keyof TableSchema["Encoded"] extends infer K
|
|
742
|
+
? K extends keyof TableSchema["Encoded"] ? [TableSchema["Encoded"][K]] extends [number | undefined] ? K
|
|
743
|
+
: never
|
|
744
|
+
: never
|
|
745
|
+
: never
|
|
746
|
+
|
|
747
|
+
const applyDelete = (query: IndexedDbQuery.Delete<any, never>) =>
|
|
748
|
+
Effect.callback<any, IndexedDbQueryError>((resume) => {
|
|
749
|
+
const database = query.delete.from.database
|
|
750
|
+
const IDBKeyRange = query.delete.from.IDBKeyRange
|
|
751
|
+
const transaction = getOrCreateTransaction(database.current, [query.delete.from.table.tableName], "readwrite", {
|
|
752
|
+
durability: query.delete.from.table.durability
|
|
753
|
+
})
|
|
754
|
+
const objectStore = transaction.objectStore(query.delete.from.table.tableName)
|
|
755
|
+
const predicate = query.predicate
|
|
756
|
+
|
|
757
|
+
let keyRange: globalThis.IDBKeyRange | undefined = undefined
|
|
758
|
+
|
|
759
|
+
if (query.only !== undefined) {
|
|
760
|
+
keyRange = IDBKeyRange.only(query.only)
|
|
761
|
+
} else if (
|
|
762
|
+
query.lowerBound !== undefined &&
|
|
763
|
+
query.upperBound !== undefined
|
|
764
|
+
) {
|
|
765
|
+
keyRange = IDBKeyRange.bound(
|
|
766
|
+
query.lowerBound,
|
|
767
|
+
query.upperBound,
|
|
768
|
+
query.excludeLowerBound,
|
|
769
|
+
query.excludeUpperBound
|
|
770
|
+
)
|
|
771
|
+
} else if (query.lowerBound !== undefined) {
|
|
772
|
+
keyRange = IDBKeyRange.lowerBound(
|
|
773
|
+
query.lowerBound,
|
|
774
|
+
query.excludeLowerBound
|
|
775
|
+
)
|
|
776
|
+
} else if (query.upperBound !== undefined) {
|
|
777
|
+
keyRange = IDBKeyRange.upperBound(
|
|
778
|
+
query.upperBound,
|
|
779
|
+
query.excludeUpperBound
|
|
780
|
+
)
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
let request: globalThis.IDBRequest
|
|
784
|
+
|
|
785
|
+
if (query.limitValue !== undefined || predicate) {
|
|
786
|
+
const cursorRequest = objectStore.openCursor()
|
|
787
|
+
let count = 0
|
|
788
|
+
|
|
789
|
+
cursorRequest.onerror = () => {
|
|
790
|
+
resume(
|
|
791
|
+
Effect.fail(
|
|
792
|
+
new IndexedDbQueryError({
|
|
793
|
+
reason: "TransactionError",
|
|
794
|
+
cause: cursorRequest.error
|
|
795
|
+
})
|
|
796
|
+
)
|
|
797
|
+
)
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
cursorRequest.onsuccess = () => {
|
|
801
|
+
const cursor = cursorRequest.result
|
|
802
|
+
if (cursor === null) {
|
|
803
|
+
return resume(Effect.void)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (predicate === undefined || predicate(cursor.value)) {
|
|
807
|
+
const deleteRequest = cursor.delete()
|
|
808
|
+
deleteRequest.onerror = () => {
|
|
809
|
+
resume(
|
|
810
|
+
Effect.fail(
|
|
811
|
+
new IndexedDbQueryError({
|
|
812
|
+
reason: "TransactionError",
|
|
813
|
+
cause: deleteRequest.error
|
|
814
|
+
})
|
|
815
|
+
)
|
|
816
|
+
)
|
|
817
|
+
}
|
|
818
|
+
count += 1
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (query.limitValue === undefined || count < query.limitValue) {
|
|
822
|
+
return cursor.continue()
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
resume(Effect.void)
|
|
826
|
+
}
|
|
827
|
+
} else if (keyRange !== undefined) {
|
|
828
|
+
request = objectStore.delete(keyRange)
|
|
829
|
+
|
|
830
|
+
request.onerror = (event) => {
|
|
831
|
+
resume(
|
|
832
|
+
Effect.fail(
|
|
833
|
+
new IndexedDbQueryError({
|
|
834
|
+
reason: "TransactionError",
|
|
835
|
+
cause: event
|
|
836
|
+
})
|
|
837
|
+
)
|
|
838
|
+
)
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
request.onsuccess = () => {
|
|
842
|
+
resume(Effect.succeed(request.result))
|
|
843
|
+
}
|
|
844
|
+
} else {
|
|
845
|
+
resume(
|
|
846
|
+
Effect.die(new Error("No key range provided for delete operation"))
|
|
847
|
+
)
|
|
848
|
+
}
|
|
849
|
+
})
|
|
850
|
+
|
|
851
|
+
const getReadonlyObjectStore = (
|
|
852
|
+
query: IndexedDbQuery.Select<any, never> | IndexedDbQuery.Count<any, never>
|
|
853
|
+
) => {
|
|
854
|
+
const database = query.from.database
|
|
855
|
+
const IDBKeyRange = query.from.IDBKeyRange
|
|
856
|
+
const transaction = getOrCreateTransaction(database.current, [query.from.table.tableName], "readonly", {
|
|
857
|
+
durability: query.from.table.durability
|
|
858
|
+
})
|
|
859
|
+
const objectStore = transaction.objectStore(query.from.table.tableName)
|
|
860
|
+
|
|
861
|
+
let keyRange: globalThis.IDBKeyRange | undefined = undefined
|
|
862
|
+
let store: globalThis.IDBObjectStore | globalThis.IDBIndex
|
|
863
|
+
|
|
864
|
+
if (query.only !== undefined) {
|
|
865
|
+
keyRange = IDBKeyRange.only(query.only)
|
|
866
|
+
} else if (query.lowerBound !== undefined && query.upperBound !== undefined) {
|
|
867
|
+
keyRange = IDBKeyRange.bound(
|
|
868
|
+
query.lowerBound,
|
|
869
|
+
query.upperBound,
|
|
870
|
+
query.excludeLowerBound,
|
|
871
|
+
query.excludeUpperBound
|
|
872
|
+
)
|
|
873
|
+
} else if (query.lowerBound !== undefined) {
|
|
874
|
+
keyRange = IDBKeyRange.lowerBound(
|
|
875
|
+
query.lowerBound,
|
|
876
|
+
query.excludeLowerBound
|
|
877
|
+
)
|
|
878
|
+
} else if (query.upperBound !== undefined) {
|
|
879
|
+
keyRange = IDBKeyRange.upperBound(
|
|
880
|
+
query.upperBound,
|
|
881
|
+
query.excludeUpperBound
|
|
882
|
+
)
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (query.index !== undefined) {
|
|
886
|
+
store = objectStore.index(query.index)
|
|
887
|
+
} else {
|
|
888
|
+
store = objectStore
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return { store, keyRange }
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const applySelect = Effect.fnUntraced(function*(
|
|
895
|
+
query: IndexedDbQuery.Select<any, never, any>
|
|
896
|
+
): Effect.fn.Return<Array<any>, IndexedDbQueryError, unknown> {
|
|
897
|
+
const keyPath = query.from.table.keyPath
|
|
898
|
+
const predicate = query.predicate
|
|
899
|
+
|
|
900
|
+
const data = predicate || keyPath === undefined || query.offsetValue !== undefined ?
|
|
901
|
+
yield* Effect.callback<Array<any>, IndexedDbQueryError>((resume) => {
|
|
902
|
+
const { keyRange, store } = getReadonlyObjectStore(query)
|
|
903
|
+
|
|
904
|
+
const cursorRequest = store.openCursor(keyRange, query.reverseValue ? "prev" : "next")
|
|
905
|
+
const results: Array<any> = []
|
|
906
|
+
let count = 0
|
|
907
|
+
let offsetApplied = false
|
|
908
|
+
|
|
909
|
+
cursorRequest.onerror = () => {
|
|
910
|
+
resume(
|
|
911
|
+
Effect.fail(
|
|
912
|
+
new IndexedDbQueryError({
|
|
913
|
+
reason: "TransactionError",
|
|
914
|
+
cause: cursorRequest.error
|
|
915
|
+
})
|
|
916
|
+
)
|
|
917
|
+
)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
cursorRequest.onsuccess = () => {
|
|
921
|
+
const cursor = cursorRequest.result
|
|
922
|
+
if (cursor === null) {
|
|
923
|
+
return resume(Effect.succeed(results))
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (query.offsetValue && !offsetApplied) {
|
|
927
|
+
offsetApplied = true
|
|
928
|
+
return cursor.advance(query.offsetValue)
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (predicate === undefined || predicate(cursor.value)) {
|
|
932
|
+
results.push(
|
|
933
|
+
keyPath === undefined
|
|
934
|
+
? { ...cursor.value, key: cursor.key }
|
|
935
|
+
: cursor.value
|
|
936
|
+
)
|
|
937
|
+
count += 1
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (query.limitValue === undefined || count < query.limitValue) {
|
|
941
|
+
return cursor.continue()
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
resume(Effect.succeed(results))
|
|
945
|
+
}
|
|
946
|
+
}) :
|
|
947
|
+
yield* Effect.callback<Array<any>, IndexedDbQueryError>((resume) => {
|
|
948
|
+
const { keyRange, store } = getReadonlyObjectStore(query)
|
|
949
|
+
const request = store.getAll(keyRange, query.limitValue)
|
|
950
|
+
request.onerror = (event) => {
|
|
951
|
+
resume(
|
|
952
|
+
Effect.fail(
|
|
953
|
+
new IndexedDbQueryError({
|
|
954
|
+
reason: "TransactionError",
|
|
955
|
+
cause: event
|
|
956
|
+
})
|
|
957
|
+
)
|
|
958
|
+
)
|
|
959
|
+
}
|
|
960
|
+
request.onsuccess = () => {
|
|
961
|
+
if (query.reverseValue) {
|
|
962
|
+
request.result.reverse()
|
|
963
|
+
}
|
|
964
|
+
resume(Effect.succeed(request.result))
|
|
965
|
+
}
|
|
966
|
+
})
|
|
967
|
+
|
|
968
|
+
const tableSchema = (query.from.table as IndexedDbTable.AnyWithProps).arraySchema
|
|
969
|
+
|
|
970
|
+
return yield* Schema.decodeUnknownEffect(tableSchema)(data).pipe(
|
|
971
|
+
Effect.mapError(
|
|
972
|
+
(error) =>
|
|
973
|
+
new IndexedDbQueryError({
|
|
974
|
+
reason: "DecodeError",
|
|
975
|
+
cause: error
|
|
976
|
+
})
|
|
977
|
+
)
|
|
978
|
+
) as Effect.Effect<Array<any>, IndexedDbQueryError, unknown>
|
|
979
|
+
})
|
|
980
|
+
|
|
981
|
+
const applyFirst = Effect.fnUntraced(function*(
|
|
982
|
+
query: IndexedDbQuery.First<any, never>
|
|
983
|
+
) {
|
|
984
|
+
const keyPath = query.select.from.table.keyPath
|
|
985
|
+
|
|
986
|
+
const data = yield* Effect.callback<any, IndexedDbQueryError | Cause.NoSuchElementError>((resume) => {
|
|
987
|
+
const { keyRange, store } = getReadonlyObjectStore(query.select)
|
|
988
|
+
|
|
989
|
+
if (keyRange !== undefined) {
|
|
990
|
+
const request = store.get(keyRange)
|
|
991
|
+
|
|
992
|
+
request.onerror = (event) => {
|
|
993
|
+
resume(
|
|
994
|
+
Effect.fail(
|
|
995
|
+
new IndexedDbQueryError({
|
|
996
|
+
reason: "TransactionError",
|
|
997
|
+
cause: event
|
|
998
|
+
})
|
|
999
|
+
)
|
|
1000
|
+
)
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
request.onsuccess = () => {
|
|
1004
|
+
resume(Effect.succeed(request.result))
|
|
1005
|
+
}
|
|
1006
|
+
} else {
|
|
1007
|
+
const request = store.openCursor()
|
|
1008
|
+
|
|
1009
|
+
request.onerror = (event) => {
|
|
1010
|
+
resume(
|
|
1011
|
+
Effect.fail(
|
|
1012
|
+
new IndexedDbQueryError({
|
|
1013
|
+
reason: "TransactionError",
|
|
1014
|
+
cause: event
|
|
1015
|
+
})
|
|
1016
|
+
)
|
|
1017
|
+
)
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
request.onsuccess = () => {
|
|
1021
|
+
const value = request.result?.value
|
|
1022
|
+
const key = request.result?.key
|
|
1023
|
+
|
|
1024
|
+
if (value === undefined) {
|
|
1025
|
+
resume(
|
|
1026
|
+
Effect.fail(new Cause.NoSuchElementError(`No such element in table ${query.select.from.table.tableName}`))
|
|
1027
|
+
)
|
|
1028
|
+
} else {
|
|
1029
|
+
resume(
|
|
1030
|
+
Effect.succeed(keyPath === undefined ? { ...value, key } : value)
|
|
1031
|
+
)
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
})
|
|
1036
|
+
|
|
1037
|
+
return yield* Schema.decodeUnknownEffect(query.select.from.table.readSchema)(
|
|
1038
|
+
data
|
|
1039
|
+
).pipe(
|
|
1040
|
+
Effect.mapError(
|
|
1041
|
+
(error) =>
|
|
1042
|
+
new IndexedDbQueryError({
|
|
1043
|
+
reason: "DecodeError",
|
|
1044
|
+
cause: error
|
|
1045
|
+
})
|
|
1046
|
+
)
|
|
1047
|
+
)
|
|
1048
|
+
})
|
|
1049
|
+
|
|
1050
|
+
const applyModify = Effect.fnUntraced(function*({
|
|
1051
|
+
query,
|
|
1052
|
+
value
|
|
1053
|
+
}: {
|
|
1054
|
+
query: IndexedDbQuery.Modify<any>
|
|
1055
|
+
value: any
|
|
1056
|
+
}) {
|
|
1057
|
+
const autoIncrement = query.from.table.autoIncrement as boolean
|
|
1058
|
+
const keyPath = query.from.table.keyPath
|
|
1059
|
+
const table = query.from.table
|
|
1060
|
+
const schema: Schema.Top = autoIncrement && value[keyPath] === undefined
|
|
1061
|
+
? table.autoincrementSchema
|
|
1062
|
+
: table.tableSchema
|
|
1063
|
+
|
|
1064
|
+
const encodedValue = yield* schema.makeEffect(value).pipe(
|
|
1065
|
+
Effect.flatMap(Schema.encodeUnknownEffect(schema)),
|
|
1066
|
+
Effect.mapError(
|
|
1067
|
+
(error) =>
|
|
1068
|
+
new IndexedDbQueryError({
|
|
1069
|
+
reason: "EncodeError",
|
|
1070
|
+
cause: error
|
|
1071
|
+
})
|
|
1072
|
+
)
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
return yield* Effect.callback<any, IndexedDbQueryError>((resume) => {
|
|
1076
|
+
const database = query.from.database
|
|
1077
|
+
const transaction = getOrCreateTransaction(database.current, [query.from.table.tableName], "readwrite", {
|
|
1078
|
+
durability: query.from.table.durability
|
|
1079
|
+
})
|
|
1080
|
+
const objectStore = transaction.objectStore(query.from.table.tableName)
|
|
1081
|
+
|
|
1082
|
+
let request: globalThis.IDBRequest<IDBValidKey>
|
|
1083
|
+
|
|
1084
|
+
if (query.operation === "add") {
|
|
1085
|
+
request = objectStore.add(
|
|
1086
|
+
encodedValue,
|
|
1087
|
+
keyPath === undefined ? value["key"] : undefined
|
|
1088
|
+
)
|
|
1089
|
+
} else if (query.operation === "put") {
|
|
1090
|
+
request = objectStore.put(
|
|
1091
|
+
encodedValue,
|
|
1092
|
+
keyPath === undefined ? value["key"] : undefined
|
|
1093
|
+
)
|
|
1094
|
+
} else {
|
|
1095
|
+
return resume(Effect.die(new Error("Invalid modify operation")))
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
request.onerror = (event) => {
|
|
1099
|
+
resume(
|
|
1100
|
+
Effect.fail(
|
|
1101
|
+
new IndexedDbQueryError({
|
|
1102
|
+
reason: "TransactionError",
|
|
1103
|
+
cause: event
|
|
1104
|
+
})
|
|
1105
|
+
)
|
|
1106
|
+
)
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
request.onsuccess = () => {
|
|
1110
|
+
resume(Effect.succeed(request.result))
|
|
1111
|
+
}
|
|
1112
|
+
})
|
|
1113
|
+
})
|
|
1114
|
+
|
|
1115
|
+
const applyModifyAll = Effect.fnUntraced(
|
|
1116
|
+
function*({
|
|
1117
|
+
query,
|
|
1118
|
+
values
|
|
1119
|
+
}: {
|
|
1120
|
+
query: IndexedDbQuery.ModifyAll<any>
|
|
1121
|
+
values: Array<any>
|
|
1122
|
+
}) {
|
|
1123
|
+
const autoIncrement = query.from.table.autoIncrement as boolean
|
|
1124
|
+
const keyPath = query.from.table.keyPath
|
|
1125
|
+
const schema = query.from.table.tableSchema
|
|
1126
|
+
const encodedValues = new Array(values.length)
|
|
1127
|
+
const makeValue = SchemaParser.makeEffect(schema)
|
|
1128
|
+
const encodeValue = SchemaParser.encodeUnknownEffect(schema)
|
|
1129
|
+
const makeValueAutoincrement = SchemaParser.makeEffect(query.from.table.autoincrementSchema)
|
|
1130
|
+
const encodeValueAutoincrement = SchemaParser.encodeUnknownEffect(query.from.table.autoincrementSchema)
|
|
1131
|
+
|
|
1132
|
+
for (let i = 0; i < values.length; i++) {
|
|
1133
|
+
const value = values[i]
|
|
1134
|
+
if (autoIncrement && value[keyPath] === undefined) {
|
|
1135
|
+
encodedValues[i] = yield* encodeValueAutoincrement(yield* makeValueAutoincrement(value))
|
|
1136
|
+
} else {
|
|
1137
|
+
encodedValues[i] = yield* encodeValue(yield* makeValue(value))
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return yield* Effect.callback<
|
|
1142
|
+
Array<globalThis.IDBValidKey>,
|
|
1143
|
+
IndexedDbQueryError
|
|
1144
|
+
>((resume) => {
|
|
1145
|
+
if (encodedValues.length === 0) {
|
|
1146
|
+
return resume(Effect.succeed([]))
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const database = query.from.database
|
|
1150
|
+
const transaction = getOrCreateTransaction(database.current, [query.from.table.tableName], "readwrite", {
|
|
1151
|
+
durability: query.from.table.durability
|
|
1152
|
+
})
|
|
1153
|
+
const objectStore = transaction.objectStore(query.from.table.tableName)
|
|
1154
|
+
|
|
1155
|
+
const results: Array<globalThis.IDBValidKey> = new Array(encodedValues.length)
|
|
1156
|
+
let remaining = encodedValues.length
|
|
1157
|
+
|
|
1158
|
+
if (query.operation === "add") {
|
|
1159
|
+
for (let i = 0; i < encodedValues.length; i++) {
|
|
1160
|
+
const request = objectStore.add(
|
|
1161
|
+
encodedValues[i],
|
|
1162
|
+
keyPath === undefined ? values[i]["key"] : undefined
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
request.onerror = () => {
|
|
1166
|
+
resume(
|
|
1167
|
+
Effect.fail(
|
|
1168
|
+
new IndexedDbQueryError({
|
|
1169
|
+
reason: "TransactionError",
|
|
1170
|
+
cause: request.error
|
|
1171
|
+
})
|
|
1172
|
+
)
|
|
1173
|
+
)
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
request.onsuccess = () => {
|
|
1177
|
+
results[i] = request.result
|
|
1178
|
+
remaining -= 1
|
|
1179
|
+
if (remaining === 0) {
|
|
1180
|
+
resume(Effect.succeed(results))
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
} else if (query.operation === "put") {
|
|
1185
|
+
for (let i = 0; i < encodedValues.length; i++) {
|
|
1186
|
+
const request = objectStore.put(
|
|
1187
|
+
encodedValues[i],
|
|
1188
|
+
keyPath === undefined ? values[i]["key"] : undefined
|
|
1189
|
+
)
|
|
1190
|
+
|
|
1191
|
+
request.onerror = () => {
|
|
1192
|
+
resume(
|
|
1193
|
+
Effect.fail(
|
|
1194
|
+
new IndexedDbQueryError({
|
|
1195
|
+
reason: "TransactionError",
|
|
1196
|
+
cause: request.error
|
|
1197
|
+
})
|
|
1198
|
+
)
|
|
1199
|
+
)
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
request.onsuccess = () => {
|
|
1203
|
+
results[i] = request.result
|
|
1204
|
+
remaining -= 1
|
|
1205
|
+
if (remaining === 0) {
|
|
1206
|
+
resume(Effect.succeed(results))
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
} else {
|
|
1211
|
+
return resume(Effect.die(new Error("Invalid modify all operation")))
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
objectStore.transaction.onerror = () => {
|
|
1215
|
+
resume(
|
|
1216
|
+
Effect.fail(
|
|
1217
|
+
new IndexedDbQueryError({
|
|
1218
|
+
reason: "TransactionError",
|
|
1219
|
+
cause: objectStore.transaction.error
|
|
1220
|
+
})
|
|
1221
|
+
)
|
|
1222
|
+
)
|
|
1223
|
+
}
|
|
1224
|
+
})
|
|
1225
|
+
},
|
|
1226
|
+
Effect.catchIf(
|
|
1227
|
+
SchemaIssue.isIssue,
|
|
1228
|
+
(issue) => Effect.fail(new IndexedDbQueryError({ reason: "EncodeError", cause: new Schema.SchemaError(issue) }))
|
|
1229
|
+
)
|
|
1230
|
+
)
|
|
1231
|
+
|
|
1232
|
+
const applyClear = (options: {
|
|
1233
|
+
readonly database: globalThis.IDBDatabase
|
|
1234
|
+
readonly table: IndexedDbTable.AnyWithProps
|
|
1235
|
+
}) =>
|
|
1236
|
+
Effect.callback<void, IndexedDbQueryError>((resume) => {
|
|
1237
|
+
const database = options.database
|
|
1238
|
+
const transaction = getOrCreateTransaction(database, [options.table.tableName], "readwrite", {
|
|
1239
|
+
durability: options.table.durability
|
|
1240
|
+
})
|
|
1241
|
+
const objectStore = transaction.objectStore(options.table.tableName)
|
|
1242
|
+
|
|
1243
|
+
const request = objectStore.clear()
|
|
1244
|
+
|
|
1245
|
+
request.onerror = (event) => {
|
|
1246
|
+
resume(
|
|
1247
|
+
Effect.fail(
|
|
1248
|
+
new IndexedDbQueryError({
|
|
1249
|
+
reason: "TransactionError",
|
|
1250
|
+
cause: event
|
|
1251
|
+
})
|
|
1252
|
+
)
|
|
1253
|
+
)
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
request.onsuccess = () => {
|
|
1257
|
+
resume(Effect.void)
|
|
1258
|
+
}
|
|
1259
|
+
})
|
|
1260
|
+
|
|
1261
|
+
const applyClearAll = (options: {
|
|
1262
|
+
readonly database: globalThis.IDBDatabase
|
|
1263
|
+
}) =>
|
|
1264
|
+
Effect.callback<void, IndexedDbQueryError>((resume) => {
|
|
1265
|
+
const database = options.database
|
|
1266
|
+
const tables = database.objectStoreNames
|
|
1267
|
+
const transaction = getOrCreateTransaction(database, [...tables], "readwrite")
|
|
1268
|
+
|
|
1269
|
+
for (let t = 0; t < tables.length; t++) {
|
|
1270
|
+
const objectStore = transaction.objectStore(tables[t])
|
|
1271
|
+
const request = objectStore.clear()
|
|
1272
|
+
|
|
1273
|
+
request.onerror = () => {
|
|
1274
|
+
resume(
|
|
1275
|
+
Effect.fail(
|
|
1276
|
+
new IndexedDbQueryError({
|
|
1277
|
+
reason: "TransactionError",
|
|
1278
|
+
cause: request.error
|
|
1279
|
+
})
|
|
1280
|
+
)
|
|
1281
|
+
)
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
transaction.onerror = () => {
|
|
1286
|
+
resume(
|
|
1287
|
+
Effect.fail(
|
|
1288
|
+
new IndexedDbQueryError({
|
|
1289
|
+
reason: "TransactionError",
|
|
1290
|
+
cause: transaction.error
|
|
1291
|
+
})
|
|
1292
|
+
)
|
|
1293
|
+
)
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
transaction.oncomplete = () => {
|
|
1297
|
+
resume(Effect.void)
|
|
1298
|
+
}
|
|
1299
|
+
})
|
|
1300
|
+
|
|
1301
|
+
const getCount = (query: IndexedDbQuery.Count<any, never>) =>
|
|
1302
|
+
Effect.callback<number, IndexedDbQueryError>((resume) => {
|
|
1303
|
+
const { keyRange, store } = getReadonlyObjectStore(query)
|
|
1304
|
+
|
|
1305
|
+
const request = store.count(keyRange)
|
|
1306
|
+
|
|
1307
|
+
request.onerror = (event) => {
|
|
1308
|
+
resume(
|
|
1309
|
+
Effect.fail(
|
|
1310
|
+
new IndexedDbQueryError({
|
|
1311
|
+
reason: "TransactionError",
|
|
1312
|
+
cause: event
|
|
1313
|
+
})
|
|
1314
|
+
)
|
|
1315
|
+
)
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
request.onsuccess = () => {
|
|
1319
|
+
resume(Effect.succeed(request.result))
|
|
1320
|
+
}
|
|
1321
|
+
})
|
|
1322
|
+
|
|
1323
|
+
const FromProto: Omit<
|
|
1324
|
+
IndexedDbQuery.From<any>,
|
|
1325
|
+
| "table"
|
|
1326
|
+
| "database"
|
|
1327
|
+
| "IDBKeyRange"
|
|
1328
|
+
| "transaction"
|
|
1329
|
+
| "reactivity"
|
|
1330
|
+
| "selectCache"
|
|
1331
|
+
| "countCache"
|
|
1332
|
+
| "deleteCache"
|
|
1333
|
+
> = {
|
|
1334
|
+
...CommonProto,
|
|
1335
|
+
select<Index extends IndexedDbDatabase.IndexFromTable<any>>(
|
|
1336
|
+
this: IndexedDbQuery.From<any>,
|
|
1337
|
+
index?: Index
|
|
1338
|
+
) {
|
|
1339
|
+
let select = this.selectCache.get(index)
|
|
1340
|
+
if (select === undefined) {
|
|
1341
|
+
select = makeSelect({
|
|
1342
|
+
from: this,
|
|
1343
|
+
index
|
|
1344
|
+
})
|
|
1345
|
+
this.selectCache.set(index, select)
|
|
1346
|
+
}
|
|
1347
|
+
return select
|
|
1348
|
+
},
|
|
1349
|
+
count<Index extends IndexedDbDatabase.IndexFromTable<any>>(
|
|
1350
|
+
this: IndexedDbQuery.From<any>,
|
|
1351
|
+
index?: Index
|
|
1352
|
+
) {
|
|
1353
|
+
let count = this.countCache.get(index)
|
|
1354
|
+
if (count === undefined) {
|
|
1355
|
+
count = makeCount({
|
|
1356
|
+
from: this,
|
|
1357
|
+
index
|
|
1358
|
+
})
|
|
1359
|
+
this.countCache.set(index, count)
|
|
1360
|
+
}
|
|
1361
|
+
return count
|
|
1362
|
+
},
|
|
1363
|
+
delete<Index extends IndexedDbDatabase.IndexFromTable<any>>(
|
|
1364
|
+
this: IndexedDbQuery.From<any>,
|
|
1365
|
+
index?: Index
|
|
1366
|
+
) {
|
|
1367
|
+
let cached = this.deleteCache.get(index)
|
|
1368
|
+
if (cached === undefined) {
|
|
1369
|
+
cached = makeDeletePartial({
|
|
1370
|
+
from: this,
|
|
1371
|
+
index
|
|
1372
|
+
})
|
|
1373
|
+
this.deleteCache.set(index, cached)
|
|
1374
|
+
}
|
|
1375
|
+
return cached
|
|
1376
|
+
},
|
|
1377
|
+
insert(this: IndexedDbQuery.From<any>, value: any) {
|
|
1378
|
+
return makeModify({ from: this, value, operation: "add" })
|
|
1379
|
+
},
|
|
1380
|
+
upsert(this: IndexedDbQuery.From<any>, value: any) {
|
|
1381
|
+
return makeModify({ from: this, value, operation: "put" })
|
|
1382
|
+
},
|
|
1383
|
+
insertAll(this: IndexedDbQuery.From<any>, values: Array<any>) {
|
|
1384
|
+
return makeModifyAll({ from: this, values, operation: "add" })
|
|
1385
|
+
},
|
|
1386
|
+
upsertAll(this: IndexedDbQuery.From<any>, values: Array<any>) {
|
|
1387
|
+
return makeModifyAll({ from: this, values, operation: "put" })
|
|
1388
|
+
},
|
|
1389
|
+
get clear() {
|
|
1390
|
+
const self = this as IndexedDbQuery.From<any>
|
|
1391
|
+
return applyClear({
|
|
1392
|
+
database: self.database.current,
|
|
1393
|
+
table: self.table
|
|
1394
|
+
})
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
const makeFrom = <
|
|
1399
|
+
const Table extends IndexedDbTable.AnyWithProps
|
|
1400
|
+
>(options: {
|
|
1401
|
+
readonly table: Table
|
|
1402
|
+
readonly database: MutableRef.MutableRef<globalThis.IDBDatabase>
|
|
1403
|
+
readonly IDBKeyRange: typeof globalThis.IDBKeyRange
|
|
1404
|
+
readonly reactivity: Reactivity.Reactivity["Service"]
|
|
1405
|
+
}): IndexedDbQuery.From<Table> => {
|
|
1406
|
+
const self = Object.create(FromProto)
|
|
1407
|
+
self.table = options.table
|
|
1408
|
+
self.database = options.database
|
|
1409
|
+
self.IDBKeyRange = options.IDBKeyRange
|
|
1410
|
+
self.reactivity = options.reactivity
|
|
1411
|
+
self.selectCache = new Map()
|
|
1412
|
+
self.countCache = new Map()
|
|
1413
|
+
self.deleteCache = new Map()
|
|
1414
|
+
return self
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
const DeletePartialProto: Omit<
|
|
1418
|
+
IndexedDbQuery.DeletePartial<any, never>,
|
|
1419
|
+
| "from"
|
|
1420
|
+
| "index"
|
|
1421
|
+
> = {
|
|
1422
|
+
...CommonProto,
|
|
1423
|
+
limit(this: IndexedDbQuery.DeletePartial<any, never>, limit: number) {
|
|
1424
|
+
return makeDelete({
|
|
1425
|
+
delete: this as any,
|
|
1426
|
+
limitValue: limit
|
|
1427
|
+
})
|
|
1428
|
+
},
|
|
1429
|
+
equals(this: IndexedDbQuery.DeletePartial<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1430
|
+
return makeDelete({
|
|
1431
|
+
delete: this as any,
|
|
1432
|
+
only: value
|
|
1433
|
+
})
|
|
1434
|
+
},
|
|
1435
|
+
gte(this: IndexedDbQuery.DeletePartial<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1436
|
+
return makeDelete({
|
|
1437
|
+
delete: this as any,
|
|
1438
|
+
lowerBound: value,
|
|
1439
|
+
excludeLowerBound: false
|
|
1440
|
+
})
|
|
1441
|
+
},
|
|
1442
|
+
lte(this: IndexedDbQuery.DeletePartial<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1443
|
+
return makeDelete({
|
|
1444
|
+
delete: this as any,
|
|
1445
|
+
upperBound: value,
|
|
1446
|
+
excludeUpperBound: false
|
|
1447
|
+
})
|
|
1448
|
+
},
|
|
1449
|
+
gt(this: IndexedDbQuery.DeletePartial<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1450
|
+
return makeDelete({
|
|
1451
|
+
delete: this as any,
|
|
1452
|
+
lowerBound: value,
|
|
1453
|
+
excludeLowerBound: true
|
|
1454
|
+
})
|
|
1455
|
+
},
|
|
1456
|
+
lt(this: IndexedDbQuery.DeletePartial<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1457
|
+
return makeDelete({
|
|
1458
|
+
delete: this as any,
|
|
1459
|
+
upperBound: value,
|
|
1460
|
+
excludeUpperBound: true
|
|
1461
|
+
})
|
|
1462
|
+
},
|
|
1463
|
+
between(
|
|
1464
|
+
this: IndexedDbQuery.DeletePartial<any, never>,
|
|
1465
|
+
lowerBound: IndexedDbQuery.ExtractIndexType<any, never>,
|
|
1466
|
+
upperBound: IndexedDbQuery.ExtractIndexType<any, never>,
|
|
1467
|
+
queryOptions?: { excludeLowerBound?: boolean; excludeUpperBound?: boolean }
|
|
1468
|
+
) {
|
|
1469
|
+
return makeDelete({
|
|
1470
|
+
delete: this as any,
|
|
1471
|
+
lowerBound,
|
|
1472
|
+
upperBound,
|
|
1473
|
+
excludeLowerBound: queryOptions?.excludeLowerBound ?? false,
|
|
1474
|
+
excludeUpperBound: queryOptions?.excludeUpperBound ?? false
|
|
1475
|
+
})
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
const makeDeletePartial = <
|
|
1480
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
1481
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>
|
|
1482
|
+
>(options: {
|
|
1483
|
+
readonly from: IndexedDbQuery.From<Table>
|
|
1484
|
+
readonly index: Index | undefined
|
|
1485
|
+
}): IndexedDbQuery.DeletePartial<Table, Index> => {
|
|
1486
|
+
const self = Object.create(DeletePartialProto)
|
|
1487
|
+
self.from = options.from
|
|
1488
|
+
self.index = options.index
|
|
1489
|
+
return self as any
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
const DeleteProto: Omit<
|
|
1493
|
+
IndexedDbQuery.Delete<any, never>,
|
|
1494
|
+
| "delete"
|
|
1495
|
+
| "limitValue"
|
|
1496
|
+
| "only"
|
|
1497
|
+
| "lowerBound"
|
|
1498
|
+
| "upperBound"
|
|
1499
|
+
| "excludeLowerBound"
|
|
1500
|
+
| "excludeUpperBound"
|
|
1501
|
+
| "predicate"
|
|
1502
|
+
> = {
|
|
1503
|
+
...CommonProto,
|
|
1504
|
+
...Effectable.Prototype<IndexedDbQuery.Delete<any, never>>({
|
|
1505
|
+
label: "IndexedDbQuery.Delete",
|
|
1506
|
+
evaluate() {
|
|
1507
|
+
return applyDelete(this)
|
|
1508
|
+
}
|
|
1509
|
+
}),
|
|
1510
|
+
limit(this: IndexedDbQuery.Delete<any, never>, limit: number) {
|
|
1511
|
+
return makeDelete({
|
|
1512
|
+
...this,
|
|
1513
|
+
limitValue: limit
|
|
1514
|
+
})
|
|
1515
|
+
},
|
|
1516
|
+
filter(this: IndexedDbQuery.Delete<any, never>, filter: (value: IndexedDbTable.Encoded<any>) => boolean) {
|
|
1517
|
+
const prev = this.predicate
|
|
1518
|
+
return makeDelete({
|
|
1519
|
+
delete: this.delete,
|
|
1520
|
+
predicate: prev ? (item) => prev(item) && filter(item) : filter
|
|
1521
|
+
})
|
|
1522
|
+
},
|
|
1523
|
+
invalidate(
|
|
1524
|
+
this: IndexedDbQuery.Delete<any, never>,
|
|
1525
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
1526
|
+
) {
|
|
1527
|
+
keys ??= this.only !== undefined
|
|
1528
|
+
? { [this.delete.from.table.tableName]: [this.only] }
|
|
1529
|
+
: [this.delete.from.table.tableName]
|
|
1530
|
+
return this.delete.from.reactivity.mutation(keys, this)
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
const makeDelete = <
|
|
1535
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
1536
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>
|
|
1537
|
+
>(options: {
|
|
1538
|
+
readonly delete: IndexedDbQuery.DeletePartial<Table, Index>
|
|
1539
|
+
readonly limitValue?: number | undefined
|
|
1540
|
+
readonly only?: IndexedDbQuery.ExtractIndexType<Table, Index> | undefined
|
|
1541
|
+
readonly lowerBound?:
|
|
1542
|
+
| IndexedDbQuery.ExtractIndexType<Table, Index>
|
|
1543
|
+
| undefined
|
|
1544
|
+
readonly upperBound?:
|
|
1545
|
+
| IndexedDbQuery.ExtractIndexType<Table, Index>
|
|
1546
|
+
| undefined
|
|
1547
|
+
readonly excludeLowerBound?: boolean | undefined
|
|
1548
|
+
readonly excludeUpperBound?: boolean | undefined
|
|
1549
|
+
readonly predicate?: ((item: IndexedDbTable.Encoded<Table>) => boolean) | undefined
|
|
1550
|
+
}): IndexedDbQuery.Delete<Table, Index> => {
|
|
1551
|
+
const self = Object.create(DeleteProto)
|
|
1552
|
+
self.delete = options.delete
|
|
1553
|
+
self.limitValue = options.limitValue
|
|
1554
|
+
self.only = options.only
|
|
1555
|
+
self.lowerBound = options.lowerBound
|
|
1556
|
+
self.upperBound = options.upperBound
|
|
1557
|
+
self.excludeLowerBound = options.excludeLowerBound ?? false
|
|
1558
|
+
self.excludeUpperBound = options.excludeUpperBound ?? false
|
|
1559
|
+
self.predicate = options.predicate
|
|
1560
|
+
return self
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
const CountProto: Omit<
|
|
1564
|
+
IndexedDbQuery.Count<any, never>,
|
|
1565
|
+
| "from"
|
|
1566
|
+
| "index"
|
|
1567
|
+
| "only"
|
|
1568
|
+
| "lowerBound"
|
|
1569
|
+
| "upperBound"
|
|
1570
|
+
| "excludeLowerBound"
|
|
1571
|
+
| "excludeUpperBound"
|
|
1572
|
+
> = {
|
|
1573
|
+
...CommonProto,
|
|
1574
|
+
...Effectable.Prototype<IndexedDbQuery.Count<any, never>>({
|
|
1575
|
+
label: "IndexedDbQuery.Count",
|
|
1576
|
+
evaluate() {
|
|
1577
|
+
return getCount(this)
|
|
1578
|
+
}
|
|
1579
|
+
}),
|
|
1580
|
+
equals(this: IndexedDbQuery.Count<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1581
|
+
return makeCount({
|
|
1582
|
+
from: this.from,
|
|
1583
|
+
index: this.index,
|
|
1584
|
+
only: value
|
|
1585
|
+
})
|
|
1586
|
+
},
|
|
1587
|
+
gte(this: IndexedDbQuery.Count<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1588
|
+
return makeCount({
|
|
1589
|
+
from: this.from,
|
|
1590
|
+
index: this.index,
|
|
1591
|
+
lowerBound: value,
|
|
1592
|
+
excludeLowerBound: false
|
|
1593
|
+
})
|
|
1594
|
+
},
|
|
1595
|
+
lte(this: IndexedDbQuery.Count<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1596
|
+
return makeCount({
|
|
1597
|
+
from: this.from,
|
|
1598
|
+
index: this.index,
|
|
1599
|
+
upperBound: value,
|
|
1600
|
+
excludeUpperBound: false
|
|
1601
|
+
})
|
|
1602
|
+
},
|
|
1603
|
+
gt(this: IndexedDbQuery.Count<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1604
|
+
return makeCount({
|
|
1605
|
+
from: this.from,
|
|
1606
|
+
index: this.index,
|
|
1607
|
+
lowerBound: value,
|
|
1608
|
+
excludeLowerBound: true
|
|
1609
|
+
})
|
|
1610
|
+
},
|
|
1611
|
+
lt(this: IndexedDbQuery.Count<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1612
|
+
return makeCount({
|
|
1613
|
+
from: this.from,
|
|
1614
|
+
index: this.index,
|
|
1615
|
+
upperBound: value,
|
|
1616
|
+
excludeUpperBound: true
|
|
1617
|
+
})
|
|
1618
|
+
},
|
|
1619
|
+
between(
|
|
1620
|
+
this: IndexedDbQuery.Count<any, never>,
|
|
1621
|
+
lowerBound: IndexedDbQuery.ExtractIndexType<any, never>,
|
|
1622
|
+
upperBound: IndexedDbQuery.ExtractIndexType<any, never>,
|
|
1623
|
+
queryOptions?: { excludeLowerBound?: boolean; excludeUpperBound?: boolean }
|
|
1624
|
+
) {
|
|
1625
|
+
return makeCount({
|
|
1626
|
+
from: this.from,
|
|
1627
|
+
index: this.index,
|
|
1628
|
+
lowerBound,
|
|
1629
|
+
upperBound,
|
|
1630
|
+
excludeLowerBound: queryOptions?.excludeLowerBound ?? false,
|
|
1631
|
+
excludeUpperBound: queryOptions?.excludeUpperBound ?? false
|
|
1632
|
+
})
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
const makeCount = <
|
|
1637
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
1638
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>
|
|
1639
|
+
>(options: {
|
|
1640
|
+
readonly from: IndexedDbQuery.From<Table>
|
|
1641
|
+
readonly index: Index | undefined
|
|
1642
|
+
readonly only?: IndexedDbQuery.ExtractIndexType<Table, Index> | undefined
|
|
1643
|
+
readonly lowerBound?:
|
|
1644
|
+
| IndexedDbQuery.ExtractIndexType<Table, Index>
|
|
1645
|
+
| undefined
|
|
1646
|
+
readonly upperBound?:
|
|
1647
|
+
| IndexedDbQuery.ExtractIndexType<Table, Index>
|
|
1648
|
+
| undefined
|
|
1649
|
+
readonly excludeLowerBound?: boolean | undefined
|
|
1650
|
+
readonly excludeUpperBound?: boolean | undefined
|
|
1651
|
+
}): IndexedDbQuery.Count<Table, Index> => {
|
|
1652
|
+
const self = Object.create(CountProto)
|
|
1653
|
+
self.from = options.from
|
|
1654
|
+
self.index = options.index
|
|
1655
|
+
self.only = options.only
|
|
1656
|
+
self.lowerBound = options.lowerBound
|
|
1657
|
+
self.upperBound = options.upperBound
|
|
1658
|
+
self.excludeLowerBound = options.excludeLowerBound
|
|
1659
|
+
self.excludeUpperBound = options.excludeUpperBound
|
|
1660
|
+
return self
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
const SelectProto: Omit<
|
|
1664
|
+
IndexedDbQuery.Select<any, never>,
|
|
1665
|
+
| "from"
|
|
1666
|
+
| "index"
|
|
1667
|
+
| "limitValue"
|
|
1668
|
+
| "reverseValue"
|
|
1669
|
+
| "only"
|
|
1670
|
+
| "lowerBound"
|
|
1671
|
+
| "upperBound"
|
|
1672
|
+
| "excludeLowerBound"
|
|
1673
|
+
| "excludeUpperBound"
|
|
1674
|
+
> = {
|
|
1675
|
+
...CommonProto,
|
|
1676
|
+
...Effectable.Prototype<IndexedDbQuery.Select<any, never>>({
|
|
1677
|
+
label: "IndexedDbQuery.Select",
|
|
1678
|
+
evaluate() {
|
|
1679
|
+
return applySelect(this)
|
|
1680
|
+
}
|
|
1681
|
+
}),
|
|
1682
|
+
limit(this: IndexedDbQuery.Select<any, never>, limit: number) {
|
|
1683
|
+
return makeSelect({
|
|
1684
|
+
...this,
|
|
1685
|
+
limitValue: limit
|
|
1686
|
+
})
|
|
1687
|
+
},
|
|
1688
|
+
offset(this: IndexedDbQuery.Select<any, never>, offset: number) {
|
|
1689
|
+
return makeSelect({
|
|
1690
|
+
...this,
|
|
1691
|
+
offsetValue: offset
|
|
1692
|
+
})
|
|
1693
|
+
},
|
|
1694
|
+
equals(this: IndexedDbQuery.Select<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1695
|
+
return makeSelect({
|
|
1696
|
+
...this,
|
|
1697
|
+
only: value
|
|
1698
|
+
})
|
|
1699
|
+
},
|
|
1700
|
+
gte(this: IndexedDbQuery.Select<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1701
|
+
return makeSelect({
|
|
1702
|
+
...this,
|
|
1703
|
+
lowerBound: value,
|
|
1704
|
+
excludeLowerBound: false
|
|
1705
|
+
})
|
|
1706
|
+
},
|
|
1707
|
+
lte(this: IndexedDbQuery.Select<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1708
|
+
return makeSelect({
|
|
1709
|
+
...this,
|
|
1710
|
+
upperBound: value,
|
|
1711
|
+
excludeUpperBound: false
|
|
1712
|
+
})
|
|
1713
|
+
},
|
|
1714
|
+
gt(this: IndexedDbQuery.Select<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1715
|
+
return makeSelect({
|
|
1716
|
+
...this,
|
|
1717
|
+
lowerBound: value,
|
|
1718
|
+
excludeLowerBound: true
|
|
1719
|
+
})
|
|
1720
|
+
},
|
|
1721
|
+
lt(this: IndexedDbQuery.Select<any, never>, value: IndexedDbQuery.ExtractIndexType<any, never>) {
|
|
1722
|
+
return makeSelect({
|
|
1723
|
+
...this,
|
|
1724
|
+
upperBound: value,
|
|
1725
|
+
excludeUpperBound: true
|
|
1726
|
+
})
|
|
1727
|
+
},
|
|
1728
|
+
between(
|
|
1729
|
+
this: IndexedDbQuery.Select<any, never>,
|
|
1730
|
+
lowerBound: IndexedDbQuery.ExtractIndexType<any, never>,
|
|
1731
|
+
upperBound: IndexedDbQuery.ExtractIndexType<any, never>,
|
|
1732
|
+
queryOptions?: { excludeLowerBound?: boolean; excludeUpperBound?: boolean }
|
|
1733
|
+
) {
|
|
1734
|
+
return makeSelect({
|
|
1735
|
+
...this,
|
|
1736
|
+
lowerBound,
|
|
1737
|
+
upperBound,
|
|
1738
|
+
excludeLowerBound: queryOptions?.excludeLowerBound ?? false,
|
|
1739
|
+
excludeUpperBound: queryOptions?.excludeUpperBound ?? false
|
|
1740
|
+
})
|
|
1741
|
+
},
|
|
1742
|
+
reverse(this: IndexedDbQuery.Select<any, never>) {
|
|
1743
|
+
return makeSelect({
|
|
1744
|
+
...this,
|
|
1745
|
+
reverseValue: true
|
|
1746
|
+
})
|
|
1747
|
+
},
|
|
1748
|
+
first(this: IndexedDbQuery.Select<any, never>) {
|
|
1749
|
+
return makeFirst({ select: this })
|
|
1750
|
+
},
|
|
1751
|
+
filter(this: IndexedDbQuery.Select<any, never>, filter: (value: IndexedDbTable.Encoded<any>) => boolean) {
|
|
1752
|
+
const prev = this.predicate
|
|
1753
|
+
return makeSelect({
|
|
1754
|
+
...this,
|
|
1755
|
+
predicate: prev ? (item) => prev(item) && filter(item) : filter
|
|
1756
|
+
})
|
|
1757
|
+
},
|
|
1758
|
+
stream(this: IndexedDbQuery.Select<any, never>, options?: {
|
|
1759
|
+
readonly chunkSize?: number | undefined
|
|
1760
|
+
}) {
|
|
1761
|
+
const limit = this.limitValue
|
|
1762
|
+
const chunkSize = Math.min(options?.chunkSize ?? 100, limit ?? Number.MAX_SAFE_INTEGER)
|
|
1763
|
+
const initial = this.limit(chunkSize)
|
|
1764
|
+
return Stream.suspend(() => {
|
|
1765
|
+
let total = 0
|
|
1766
|
+
return Stream.paginate(initial, (select) =>
|
|
1767
|
+
Effect.map(
|
|
1768
|
+
applySelect(select as any),
|
|
1769
|
+
(data) => {
|
|
1770
|
+
total += data.length
|
|
1771
|
+
;(select as any).offsetValue = total
|
|
1772
|
+
const reachedLimit = limit && total >= limit
|
|
1773
|
+
const isPartial = data.length < chunkSize
|
|
1774
|
+
return [data, isPartial || reachedLimit ? Option.none() : Option.some(select)] as const
|
|
1775
|
+
}
|
|
1776
|
+
))
|
|
1777
|
+
})
|
|
1778
|
+
},
|
|
1779
|
+
reactive(
|
|
1780
|
+
this: IndexedDbQuery.Select<any, never>,
|
|
1781
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
1782
|
+
) {
|
|
1783
|
+
keys ??= [this.from.table.tableName]
|
|
1784
|
+
return this.from.reactivity.stream(keys, this)
|
|
1785
|
+
},
|
|
1786
|
+
reactiveQueue(
|
|
1787
|
+
this: IndexedDbQuery.Select<any, never>,
|
|
1788
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
1789
|
+
) {
|
|
1790
|
+
keys ??= [this.from.table.tableName]
|
|
1791
|
+
return this.from.reactivity.query(keys, this)
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
const makeSelect = <
|
|
1796
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
1797
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>
|
|
1798
|
+
>(options: {
|
|
1799
|
+
readonly from: IndexedDbQuery.From<Table>
|
|
1800
|
+
readonly index?: Index | undefined
|
|
1801
|
+
readonly limitValue?: number | undefined
|
|
1802
|
+
readonly offsetValue?: number | undefined
|
|
1803
|
+
readonly reverseValue?: boolean | undefined
|
|
1804
|
+
readonly only?: IndexedDbQuery.ExtractIndexType<Table, Index> | undefined
|
|
1805
|
+
readonly lowerBound?:
|
|
1806
|
+
| IndexedDbQuery.ExtractIndexType<Table, Index>
|
|
1807
|
+
| undefined
|
|
1808
|
+
readonly upperBound?:
|
|
1809
|
+
| IndexedDbQuery.ExtractIndexType<Table, Index>
|
|
1810
|
+
| undefined
|
|
1811
|
+
readonly excludeLowerBound?: boolean | undefined
|
|
1812
|
+
readonly excludeUpperBound?: boolean | undefined
|
|
1813
|
+
readonly predicate?: ((item: IndexedDbTable.Encoded<Table>) => boolean) | undefined
|
|
1814
|
+
}): IndexedDbQuery.Select<Table, Index> => {
|
|
1815
|
+
const self = Object.create(SelectProto)
|
|
1816
|
+
self.from = options.from
|
|
1817
|
+
self.index = options.index
|
|
1818
|
+
self.only = options.only
|
|
1819
|
+
self.limitValue = options.limitValue
|
|
1820
|
+
self.offsetValue = options.offsetValue
|
|
1821
|
+
self.reverseValue = options.reverseValue
|
|
1822
|
+
self.lowerBound = options.lowerBound
|
|
1823
|
+
self.upperBound = options.upperBound
|
|
1824
|
+
self.excludeLowerBound = options.excludeLowerBound
|
|
1825
|
+
self.excludeUpperBound = options.excludeUpperBound
|
|
1826
|
+
self.predicate = options.predicate
|
|
1827
|
+
return self as any
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const FirstProto: Omit<
|
|
1831
|
+
IndexedDbQuery.First<any, never>,
|
|
1832
|
+
"select"
|
|
1833
|
+
> = {
|
|
1834
|
+
...CommonProto,
|
|
1835
|
+
...Effectable.Prototype<IndexedDbQuery.First<any, never>>({
|
|
1836
|
+
label: "IndexedDbQuery.First",
|
|
1837
|
+
evaluate() {
|
|
1838
|
+
return applyFirst(this)
|
|
1839
|
+
}
|
|
1840
|
+
}),
|
|
1841
|
+
reactive(
|
|
1842
|
+
this: IndexedDbQuery.First<any, never>,
|
|
1843
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
1844
|
+
) {
|
|
1845
|
+
keys ??= this.select.only !== undefined
|
|
1846
|
+
? [`${this.select.from.table.tableName}:${String(this.select.only)}`]
|
|
1847
|
+
: [this.select.from.table.tableName]
|
|
1848
|
+
return this.select.from.reactivity.stream(keys, this)
|
|
1849
|
+
},
|
|
1850
|
+
reactiveQueue(
|
|
1851
|
+
this: IndexedDbQuery.First<any, never>,
|
|
1852
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
1853
|
+
) {
|
|
1854
|
+
keys ??= this.select.only !== undefined
|
|
1855
|
+
? [`${this.select.from.table.tableName}:${this.select.only}`]
|
|
1856
|
+
: [this.select.from.table.tableName]
|
|
1857
|
+
return this.select.from.reactivity.query(keys, this)
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
const makeFirst = <
|
|
1862
|
+
Table extends IndexedDbTable.AnyWithProps,
|
|
1863
|
+
Index extends IndexedDbDatabase.IndexFromTable<Table>
|
|
1864
|
+
>(options: {
|
|
1865
|
+
readonly select: IndexedDbQuery.Select<Table, Index>
|
|
1866
|
+
}): IndexedDbQuery.First<Table, Index> => {
|
|
1867
|
+
const self = Object.create(FirstProto)
|
|
1868
|
+
self.select = options.select
|
|
1869
|
+
return self as any
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
const ModifyProto: Omit<
|
|
1873
|
+
IndexedDbQuery.Modify<any>,
|
|
1874
|
+
| "from"
|
|
1875
|
+
| "value"
|
|
1876
|
+
| "operation"
|
|
1877
|
+
> = {
|
|
1878
|
+
...CommonProto,
|
|
1879
|
+
...Effectable.Prototype<IndexedDbQuery.Modify<any>>({
|
|
1880
|
+
label: "IndexedDbQuery.Modify",
|
|
1881
|
+
evaluate() {
|
|
1882
|
+
return applyModify({ query: this, value: this.value })
|
|
1883
|
+
}
|
|
1884
|
+
}),
|
|
1885
|
+
invalidate(
|
|
1886
|
+
this: IndexedDbQuery.Modify<any>,
|
|
1887
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
1888
|
+
) {
|
|
1889
|
+
const keyPath = this.from.table.keyPath
|
|
1890
|
+
keys ??= typeof keyPath === "string" && this.value[keyPath] !== undefined
|
|
1891
|
+
? { [this.from.table.tableName]: [this.value[keyPath]] }
|
|
1892
|
+
: [this.from.table.tableName]
|
|
1893
|
+
return this.from.reactivity.mutation(keys, this)
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
const makeModify = <Table extends IndexedDbTable.AnyWithProps>(options: {
|
|
1898
|
+
readonly from: IndexedDbQuery.From<Table>
|
|
1899
|
+
readonly value: IndexedDbTable.TableSchema<Table>["Type"]
|
|
1900
|
+
readonly operation: "add" | "put"
|
|
1901
|
+
}): IndexedDbQuery.Modify<Table> => {
|
|
1902
|
+
const self = Object.create(ModifyProto)
|
|
1903
|
+
self.from = options.from
|
|
1904
|
+
self.value = options.value
|
|
1905
|
+
self.operation = options.operation
|
|
1906
|
+
return self as any
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
const ModifyAllProto: Omit<
|
|
1910
|
+
IndexedDbQuery.ModifyAll<any>,
|
|
1911
|
+
| "from"
|
|
1912
|
+
| "values"
|
|
1913
|
+
| "operation"
|
|
1914
|
+
> = {
|
|
1915
|
+
...CommonProto,
|
|
1916
|
+
...Effectable.Prototype<IndexedDbQuery.ModifyAll<any>>({
|
|
1917
|
+
label: "IndexedDbQuery.ModifyAll",
|
|
1918
|
+
evaluate() {
|
|
1919
|
+
return applyModifyAll({ query: this, values: this.values })
|
|
1920
|
+
}
|
|
1921
|
+
}),
|
|
1922
|
+
invalidate(
|
|
1923
|
+
this: IndexedDbQuery.ModifyAll<any>,
|
|
1924
|
+
keys?: ReadonlyArray<unknown> | Record.ReadonlyRecord<string, ReadonlyArray<unknown>> | undefined
|
|
1925
|
+
) {
|
|
1926
|
+
keys ??= [this.from.table.tableName]
|
|
1927
|
+
return this.from.reactivity.mutation(keys, this)
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
const makeModifyAll = <
|
|
1932
|
+
Table extends IndexedDbTable.AnyWithProps
|
|
1933
|
+
>(options: {
|
|
1934
|
+
readonly from: IndexedDbQuery.From<Table>
|
|
1935
|
+
readonly values: Array<IndexedDbTable.TableSchema<Table>["Type"]>
|
|
1936
|
+
readonly operation: "add" | "put"
|
|
1937
|
+
}): IndexedDbQuery.ModifyAll<Table> => {
|
|
1938
|
+
const self = Object.create(ModifyAllProto)
|
|
1939
|
+
self.from = options.from
|
|
1940
|
+
self.values = options.values
|
|
1941
|
+
self.operation = options.operation
|
|
1942
|
+
return self as any
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
const QueryBuilderProto: Omit<
|
|
1946
|
+
IndexedDbQueryBuilder<any>,
|
|
1947
|
+
| "fromCache"
|
|
1948
|
+
| "tables"
|
|
1949
|
+
| "database"
|
|
1950
|
+
| "IDBKeyRange"
|
|
1951
|
+
| "IDBTransaction"
|
|
1952
|
+
| "reactivity"
|
|
1953
|
+
> = {
|
|
1954
|
+
...CommonProto,
|
|
1955
|
+
use(this: IndexedDbQueryBuilder<any>, f: (database: globalThis.IDBDatabase) => any) {
|
|
1956
|
+
return Effect.try({
|
|
1957
|
+
try: () => f(this.database.current),
|
|
1958
|
+
catch: (error) =>
|
|
1959
|
+
new IndexedDbQueryError({
|
|
1960
|
+
reason: "UnknownError",
|
|
1961
|
+
cause: error
|
|
1962
|
+
})
|
|
1963
|
+
})
|
|
1964
|
+
},
|
|
1965
|
+
from(this: IndexedDbQueryBuilder<any>, table: any) {
|
|
1966
|
+
let cached = this.fromCache.get(table)
|
|
1967
|
+
if (cached === undefined) {
|
|
1968
|
+
cached = makeFrom({
|
|
1969
|
+
database: this.database,
|
|
1970
|
+
IDBKeyRange: this.IDBKeyRange,
|
|
1971
|
+
table: this.tables.get(table)!,
|
|
1972
|
+
reactivity: this.reactivity
|
|
1973
|
+
})
|
|
1974
|
+
this.fromCache.set(table, cached)
|
|
1975
|
+
}
|
|
1976
|
+
return cached as any
|
|
1977
|
+
},
|
|
1978
|
+
get clearAll() {
|
|
1979
|
+
const self = this as IndexedDbQueryBuilder<any>
|
|
1980
|
+
return applyClearAll({ database: self.database.current })
|
|
1981
|
+
},
|
|
1982
|
+
withTransaction(this: IndexedDbQueryBuilder<any>, options: {
|
|
1983
|
+
readonly tables: NonEmptyReadonlyArray<any>
|
|
1984
|
+
readonly mode: globalThis.IDBTransactionMode
|
|
1985
|
+
readonly durability?: IDBTransactionDurability
|
|
1986
|
+
}) {
|
|
1987
|
+
return (effect) =>
|
|
1988
|
+
Effect.suspend(() => {
|
|
1989
|
+
const transaction = this.database.current.transaction(options.tables, options.mode, options)
|
|
1990
|
+
return Effect.provideService(effect, IndexedDbTransaction, transaction)
|
|
1991
|
+
}).pipe(
|
|
1992
|
+
// To prevent async gaps between transaction queries
|
|
1993
|
+
Effect.provideService(References.PreventSchedulerYield, true)
|
|
1994
|
+
)
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
/**
|
|
1999
|
+
* Creates an `IndexedDbQueryBuilder` from an open database reference, key-range constructor, table map, and reactivity service.
|
|
2000
|
+
*
|
|
2001
|
+
* @category constructors
|
|
2002
|
+
* @since 4.0.0
|
|
2003
|
+
*/
|
|
2004
|
+
export const make = <Source extends IndexedDbVersion.AnyWithProps>({
|
|
2005
|
+
IDBKeyRange,
|
|
2006
|
+
database,
|
|
2007
|
+
tables,
|
|
2008
|
+
reactivity
|
|
2009
|
+
}: {
|
|
2010
|
+
readonly database: MutableRef.MutableRef<globalThis.IDBDatabase>
|
|
2011
|
+
readonly IDBKeyRange: typeof globalThis.IDBKeyRange
|
|
2012
|
+
readonly tables: ReadonlyMap<string, IndexedDbVersion.Tables<Source>>
|
|
2013
|
+
readonly reactivity: Reactivity.Reactivity["Service"]
|
|
2014
|
+
}): IndexedDbQueryBuilder<Source> => {
|
|
2015
|
+
const self = Object.create(QueryBuilderProto)
|
|
2016
|
+
self.tables = tables
|
|
2017
|
+
self.database = database
|
|
2018
|
+
self.reactivity = reactivity
|
|
2019
|
+
self.IDBKeyRange = IDBKeyRange
|
|
2020
|
+
self.fromCache = new Map()
|
|
2021
|
+
return self
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
const getOrCreateTransaction = (
|
|
2025
|
+
database: globalThis.IDBDatabase,
|
|
2026
|
+
tables: ReadonlyArray<string>,
|
|
2027
|
+
mode: globalThis.IDBTransactionMode,
|
|
2028
|
+
options?: IDBTransactionOptions
|
|
2029
|
+
) => {
|
|
2030
|
+
const fiber = Fiber.getCurrent()!
|
|
2031
|
+
return Context.getOrUndefined(fiber.context, IndexedDbTransaction) ?? database.transaction(tables, mode, options)
|
|
2032
|
+
}
|