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

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