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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/BrowserCrypto.d.ts +81 -0
  2. package/dist/BrowserCrypto.d.ts.map +1 -0
  3. package/dist/BrowserCrypto.js +119 -0
  4. package/dist/BrowserCrypto.js.map +1 -0
  5. package/dist/BrowserHttpClient.d.ts +40 -16
  6. package/dist/BrowserHttpClient.d.ts.map +1 -1
  7. package/dist/BrowserHttpClient.js +74 -18
  8. package/dist/BrowserHttpClient.js.map +1 -1
  9. package/dist/BrowserKeyValueStore.d.ts +37 -14
  10. package/dist/BrowserKeyValueStore.d.ts.map +1 -1
  11. package/dist/BrowserKeyValueStore.js +185 -10
  12. package/dist/BrowserKeyValueStore.js.map +1 -1
  13. package/dist/BrowserPersistence.d.ts +40 -0
  14. package/dist/BrowserPersistence.d.ts.map +1 -0
  15. package/dist/BrowserPersistence.js +207 -0
  16. package/dist/BrowserPersistence.js.map +1 -0
  17. package/dist/BrowserRuntime.d.ts +79 -4
  18. package/dist/BrowserRuntime.d.ts.map +1 -1
  19. package/dist/BrowserRuntime.js +19 -1
  20. package/dist/BrowserRuntime.js.map +1 -1
  21. package/dist/BrowserSocket.d.ts +64 -6
  22. package/dist/BrowserSocket.d.ts.map +1 -1
  23. package/dist/BrowserSocket.js +64 -6
  24. package/dist/BrowserSocket.js.map +1 -1
  25. package/dist/BrowserStream.d.ts +48 -5
  26. package/dist/BrowserStream.d.ts.map +1 -1
  27. package/dist/BrowserStream.js +48 -5
  28. package/dist/BrowserStream.js.map +1 -1
  29. package/dist/BrowserWorker.d.ts +25 -4
  30. package/dist/BrowserWorker.d.ts.map +1 -1
  31. package/dist/BrowserWorker.js +62 -6
  32. package/dist/BrowserWorker.js.map +1 -1
  33. package/dist/BrowserWorkerRunner.d.ts +31 -6
  34. package/dist/BrowserWorkerRunner.d.ts.map +1 -1
  35. package/dist/BrowserWorkerRunner.js +65 -10
  36. package/dist/BrowserWorkerRunner.js.map +1 -1
  37. package/dist/Clipboard.d.ts +77 -13
  38. package/dist/Clipboard.d.ts.map +1 -1
  39. package/dist/Clipboard.js +57 -11
  40. package/dist/Clipboard.js.map +1 -1
  41. package/dist/Geolocation.d.ts +101 -24
  42. package/dist/Geolocation.d.ts.map +1 -1
  43. package/dist/Geolocation.js +69 -16
  44. package/dist/Geolocation.js.map +1 -1
  45. package/dist/IndexedDb.d.ts +89 -0
  46. package/dist/IndexedDb.d.ts.map +1 -0
  47. package/dist/IndexedDb.js +99 -0
  48. package/dist/IndexedDb.js.map +1 -0
  49. package/dist/IndexedDbDatabase.d.ts +169 -0
  50. package/dist/IndexedDbDatabase.d.ts.map +1 -0
  51. package/dist/IndexedDbDatabase.js +343 -0
  52. package/dist/IndexedDbDatabase.js.map +1 -0
  53. package/dist/IndexedDbQueryBuilder.d.ts +438 -0
  54. package/dist/IndexedDbQueryBuilder.d.ts.map +1 -0
  55. package/dist/IndexedDbQueryBuilder.js +935 -0
  56. package/dist/IndexedDbQueryBuilder.js.map +1 -0
  57. package/dist/IndexedDbTable.d.ts +191 -0
  58. package/dist/IndexedDbTable.d.ts.map +1 -0
  59. package/dist/IndexedDbTable.js +96 -0
  60. package/dist/IndexedDbTable.js.map +1 -0
  61. package/dist/IndexedDbVersion.d.ts +94 -0
  62. package/dist/IndexedDbVersion.d.ts.map +1 -0
  63. package/dist/IndexedDbVersion.js +39 -0
  64. package/dist/IndexedDbVersion.js.map +1 -0
  65. package/dist/Permissions.d.ts +76 -16
  66. package/dist/Permissions.d.ts.map +1 -1
  67. package/dist/Permissions.js +66 -11
  68. package/dist/Permissions.js.map +1 -1
  69. package/dist/index.d.ts +38 -10
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +38 -10
  72. package/dist/index.js.map +1 -1
  73. package/package.json +4 -3
  74. package/src/BrowserCrypto.ts +129 -0
  75. package/src/BrowserHttpClient.ts +84 -24
  76. package/src/BrowserKeyValueStore.ts +201 -12
  77. package/src/BrowserPersistence.ts +376 -0
  78. package/src/BrowserRuntime.ts +79 -4
  79. package/src/BrowserSocket.ts +64 -6
  80. package/src/BrowserStream.ts +48 -5
  81. package/src/BrowserWorker.ts +62 -6
  82. package/src/BrowserWorkerRunner.ts +65 -10
  83. package/src/Clipboard.ts +74 -13
  84. package/src/Geolocation.ts +97 -20
  85. package/src/IndexedDb.ts +127 -0
  86. package/src/IndexedDbDatabase.ts +664 -0
  87. package/src/IndexedDbQueryBuilder.ts +2048 -0
  88. package/src/IndexedDbTable.ts +285 -0
  89. package/src/IndexedDbVersion.ts +133 -0
  90. package/src/Permissions.ts +71 -14
  91. package/src/index.ts +45 -10
@@ -1,13 +1,45 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Browser implementations of the Effect `HttpClient`.
3
+ *
4
+ * This module exposes HTTP client layers for code that runs in a browser. It
5
+ * re-exports the fetch-based client for the common case, where requests should
6
+ * use the platform `fetch` implementation and optional `RequestInit` defaults,
7
+ * and it provides an `XMLHttpRequest`-backed layer for integrations that need
8
+ * XHR semantics such as response type control or environments where XHR is the
9
+ * required transport.
10
+ *
11
+ * Use these layers for single-page applications, browser tests, generated
12
+ * `HttpApiClient`s, and other client-side Effect programs that need HTTP
13
+ * requests to participate in interruption, typed transport / decode errors, and
14
+ * Effect's response body readers.
15
+ *
16
+ * Browser networking rules still apply. Cross-origin requests are subject to
17
+ * CORS preflights and server allowlists, especially when using custom headers,
18
+ * non-simple methods, or non-simple content types. Only CORS-exposed response
19
+ * headers are readable, and cookies / authentication are controlled by the
20
+ * browser and the configured fetch `RequestInit.credentials` policy. The XHR
21
+ * layer uses the browser's `XMLHttpRequest` defaults for credentials.
22
+ *
23
+ * Body handling differs between the transports. Fetch delegates body framing to
24
+ * the Web Fetch implementation. The XHR client sends empty, raw, `Uint8Array`,
25
+ * and `FormData` bodies directly, buffers `Stream` request bodies before
26
+ * sending, defaults responses to text, and can be switched to `ArrayBuffer`
27
+ * responses with `withXHRArrayBuffer`. When sending `FormData`, avoid setting
28
+ * an incompatible `Content-Type` header so the browser can generate the
29
+ * multipart boundary.
30
+ *
31
+ * @since 4.0.0
3
32
  */
4
33
  import * as Cause from "effect/Cause"
34
+ import * as Context from "effect/Context"
5
35
  import * as Effect from "effect/Effect"
6
36
  import type { LazyArg } from "effect/Function"
7
37
  import * as Inspectable from "effect/Inspectable"
8
38
  import type * as Layer from "effect/Layer"
39
+ import * as Option from "effect/Option"
40
+ import { type Pipeable, pipeArguments } from "effect/Pipeable"
9
41
  import * as Queue from "effect/Queue"
10
- import * as ServiceMap from "effect/ServiceMap"
42
+ import type * as Schema from "effect/Schema"
11
43
  import * as Stream from "effect/Stream"
12
44
  import * as Cookies from "effect/unstable/http/Cookies"
13
45
  import * as Headers from "effect/unstable/http/Headers"
@@ -25,18 +57,24 @@ import * as HeaderParser from "multipasta/HeadersParser"
25
57
 
26
58
  export {
27
59
  /**
28
- * @since 1.0.0
60
+ * Context reference for the `fetch` implementation used by the fetch-based HTTP client.
61
+ *
29
62
  * @category Fetch
63
+ * @since 4.0.0
30
64
  */
31
65
  Fetch,
32
66
  /**
33
- * @since 1.0.0
67
+ * Layer that provides an `HttpClient` implementation backed by the configured `Fetch` function.
68
+ *
34
69
  * @category Fetch
70
+ * @since 4.0.0
35
71
  */
36
72
  layer as layerFetch,
37
73
  /**
38
- * @since 1.0.0
74
+ * Service containing default `RequestInit` options for the fetch-based HTTP client.
75
+ *
39
76
  * @category Fetch
77
+ * @since 4.0.0
40
78
  */
41
79
  RequestInit
42
80
  } from "effect/unstable/http/FetchHttpClient"
@@ -46,23 +84,37 @@ export {
46
84
  // =============================================================================
47
85
 
48
86
  /**
49
- * @since 1.0.0
50
- * @category Models
87
+ * Allowed response body modes for the browser XHR HTTP client.
88
+ *
89
+ * @category models
90
+ * @since 4.0.0
51
91
  */
52
92
  export type XHRResponseType = "arraybuffer" | "text"
53
93
 
54
94
  /**
55
- * @since 1.0.0
56
- * @category References
95
+ * Reference that controls the `XMLHttpRequest.responseType` used by the browser XHR HTTP client, defaulting to `"text"`.
96
+ *
97
+ * **When to use**
98
+ *
99
+ * Use when XHR-backed HTTP requests need to receive response bodies as text or
100
+ * as raw `ArrayBuffer` values.
101
+ *
102
+ * @see {@link XHRResponseType} for the allowed response body modes
103
+ * @see {@link withXHRArrayBuffer} for scoping XHR response handling to `ArrayBuffer`
104
+ *
105
+ * @category references
106
+ * @since 4.0.0
57
107
  */
58
- export const CurrentXHRResponseType: ServiceMap.Reference<XHRResponseType> = ServiceMap.Reference(
108
+ export const CurrentXHRResponseType: Context.Reference<XHRResponseType> = Context.Reference(
59
109
  "@effect/platform-browser/BrowserHttpClient/CurrentXHRResponseType",
60
110
  { defaultValue: (): XHRResponseType => "text" }
61
111
  )
62
112
 
63
113
  /**
64
- * @since 1.0.0
65
- * @category References
114
+ * Runs an effect with `CurrentXHRResponseType` set to `"arraybuffer"` so the XHR HTTP client receives response bodies as `ArrayBuffer` values.
115
+ *
116
+ * @category references
117
+ * @since 4.0.0
66
118
  */
67
119
  export const withXHRArrayBuffer = <A, E, R>(
68
120
  self: Effect.Effect<A, E, R>
@@ -74,10 +126,12 @@ export const withXHRArrayBuffer = <A, E, R>(
74
126
  )
75
127
 
76
128
  /**
77
- * @since 1.0.0
78
- * @category Services
129
+ * Service tag for the `XMLHttpRequest` constructor used by the browser XHR HTTP client.
130
+ *
131
+ * @category services
132
+ * @since 4.0.0
79
133
  */
80
- export class XMLHttpRequest extends ServiceMap.Service<
134
+ export class XMLHttpRequest extends Context.Service<
81
135
  XMLHttpRequest,
82
136
  LazyArg<globalThis.XMLHttpRequest>
83
137
  >()("@effect/platform-browser/BrowserHttpClient/XMLHttpRequest") {}
@@ -87,8 +141,8 @@ const makeXhrRequest = () => new globalThis.XMLHttpRequest()
87
141
  const makeXmlHttpRequest = HttpClient.make(
88
142
  (request, url, signal, fiber) =>
89
143
  Effect.suspend(() => {
90
- const xhr = ServiceMap.getOrElse(
91
- fiber.services,
144
+ const xhr = Context.getOrElse(
145
+ fiber.context,
92
146
  XMLHttpRequest,
93
147
  () => makeXhrRequest
94
148
  )()
@@ -213,7 +267,7 @@ abstract class IncomingMessageImpl<E> extends Inspectable.Class implements HttpI
213
267
  }
214
268
 
215
269
  get remoteAddress() {
216
- return undefined
270
+ return Option.none()
217
271
  }
218
272
 
219
273
  _textEffect: Effect.Effect<string, E> | undefined
@@ -247,10 +301,10 @@ abstract class IncomingMessageImpl<E> extends Inspectable.Class implements HttpI
247
301
  )
248
302
  }
249
303
 
250
- get json(): Effect.Effect<unknown, E> {
304
+ get json(): Effect.Effect<Schema.Json, E> {
251
305
  return Effect.flatMap(this.text, (text) =>
252
306
  Effect.try({
253
- try: () => text === "" ? null : JSON.parse(text) as unknown,
307
+ try: () => text === "" ? null : JSON.parse(text),
254
308
  catch: this.onError
255
309
  }))
256
310
  }
@@ -334,7 +388,7 @@ abstract class IncomingMessageImpl<E> extends Inspectable.Class implements HttpI
334
388
  }
335
389
 
336
390
  class ClientResponseImpl extends IncomingMessageImpl<HttpClientError.HttpClientError>
337
- implements HttpClientResponse.HttpClientResponse
391
+ implements HttpClientResponse.HttpClientResponse, Pipeable
338
392
  {
339
393
  readonly [HttpClientResponse.TypeId]: typeof HttpClientResponse.TypeId
340
394
  readonly request: HttpClientRequest.HttpClientRequest
@@ -374,12 +428,18 @@ class ClientResponseImpl extends IncomingMessageImpl<HttpClientError.HttpClientE
374
428
  status: this.status
375
429
  })
376
430
  }
431
+
432
+ pipe() {
433
+ return pipeArguments(this, arguments)
434
+ }
377
435
  }
378
436
 
379
437
  /**
380
- * @since 1.0.0
381
- * @category Layers
438
+ * Layer that provides an `HttpClient` implementation backed by the browser `XMLHttpRequest` API.
439
+ *
440
+ * @category layers
441
+ * @since 4.0.0
382
442
  */
383
- export const layerXMLHttpRequest: Layer.Layer<HttpClient.HttpClient> = HttpClient.layerMergedServices(
443
+ export const layerXMLHttpRequest: Layer.Layer<HttpClient.HttpClient> = HttpClient.layerMergedContext(
384
444
  Effect.succeed(makeXmlHttpRequest)
385
445
  )
@@ -1,29 +1,218 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Browser-backed `KeyValueStore` layers for client-side Effect programs.
3
+ *
4
+ * This module provides browser implementations of the unstable persistence
5
+ * `KeyValueStore` service. Use {@link layerLocalStorage} for small
6
+ * origin-scoped values that should survive reloads and browser restarts, use
7
+ * {@link layerSessionStorage} for tab / page-session state, and use
8
+ * {@link layerIndexedDb} when the store should be asynchronous and backed by
9
+ * IndexedDB.
10
+ *
11
+ * ## Mental model
12
+ *
13
+ * All exports provide the same `KeyValueStore.KeyValueStore` service; the layer
14
+ * chooses the browser storage backend. The Web Storage layers delegate to
15
+ * `globalThis.localStorage` or `globalThis.sessionStorage` and adapt the
16
+ * string-only API, encoding `Uint8Array` values as base64. The IndexedDB layer
17
+ * stores strings and `Uint8Array` values in an object store and requires the
18
+ * browser `IndexedDb` service to open the database.
19
+ *
20
+ * ## Common tasks
21
+ *
22
+ * - Persist user preferences, lightweight caches, or drafts with
23
+ * {@link layerLocalStorage}.
24
+ * - Keep tab-scoped workflow state with {@link layerSessionStorage}.
25
+ * - Avoid blocking the main thread for larger client-side stores by using
26
+ * {@link layerIndexedDb}.
27
+ *
28
+ * ## Gotchas
29
+ *
30
+ * These layers only work where browser storage APIs are available. Browsers may
31
+ * deny storage in private modes, sandboxed frames, disabled-storage settings, or
32
+ * quota-limited contexts. Web Storage is synchronous and origin-scoped, so keep
33
+ * payloads small and do not use it as a secure store for secrets. IndexedDB is
34
+ * asynchronous but can still be blocked by permissions, quota limits, version
35
+ * upgrades, or other open tabs.
36
+ *
37
+ * **Example** (Provide localStorage to a program)
38
+ *
39
+ * ```ts
40
+ * import { BrowserKeyValueStore } from "@effect/platform-browser"
41
+ * import { Effect } from "effect"
42
+ * import { KeyValueStore } from "effect/unstable/persistence"
43
+ *
44
+ * const program = Effect.gen(function*() {
45
+ * const store = yield* KeyValueStore.KeyValueStore
46
+ * yield* store.set("theme", "dark")
47
+ * return yield* store.get("theme")
48
+ * }).pipe(
49
+ * Effect.provide(BrowserKeyValueStore.layerLocalStorage)
50
+ * )
51
+ * ```
52
+ *
53
+ * @since 4.0.0
3
54
  */
4
- import type * as Layer from "effect/Layer"
55
+ import * as Effect from "effect/Effect"
56
+ import * as Layer from "effect/Layer"
5
57
  import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore"
58
+ import { IndexedDb } from "./IndexedDb.ts"
6
59
 
7
60
  /**
8
- * Creates a `KeyValueStore` layer that uses the browser's `localStorage` api.
61
+ * Creates a `KeyValueStore` layer that uses the browser's `localStorage` API and stores values between browser sessions.
9
62
  *
10
- * Values are stored between sessions.
11
- *
12
- * @since 1.0.0
13
- * @category Layers
63
+ * @category layers
64
+ * @since 4.0.0
14
65
  */
15
66
  export const layerLocalStorage: Layer.Layer<KeyValueStore.KeyValueStore> = KeyValueStore.layerStorage(() =>
16
67
  globalThis.localStorage
17
68
  )
18
69
 
19
70
  /**
20
- * Creates a `KeyValueStore` layer that uses the browser's `sessionStorage` api.
21
- *
22
- * Values are stored only for the current session.
71
+ * Creates a `KeyValueStore` layer that uses the browser's `sessionStorage` API and stores values only for the current session.
23
72
  *
24
- * @since 1.0.0
25
- * @category Layers
73
+ * @category layers
74
+ * @since 4.0.0
26
75
  */
27
76
  export const layerSessionStorage: Layer.Layer<KeyValueStore.KeyValueStore> = KeyValueStore.layerStorage(() =>
28
77
  globalThis.sessionStorage
29
78
  )
79
+
80
+ /**
81
+ * Creates a `KeyValueStore` layer backed by IndexedDB.
82
+ *
83
+ * **When to use**
84
+ *
85
+ * Use when a browser `KeyValueStore` needs persistent asynchronous IndexedDB
86
+ * storage instead of the synchronous Web Storage APIs.
87
+ *
88
+ * **Details**
89
+ *
90
+ * The database name defaults to `"effect_key_value_store"`. The layer requires
91
+ * the `IndexedDb` service and stores string and `Uint8Array` values in the same
92
+ * backing object store.
93
+ *
94
+ * **Gotchas**
95
+ *
96
+ * IndexedDB may be unavailable or blocked by browser settings, private browsing,
97
+ * quota limits, or restricted contexts. The string and `Uint8Array` accessors do
98
+ * not coerce values stored with the other representation.
99
+ *
100
+ * @see {@link layerLocalStorage} for synchronous persistent Web Storage
101
+ * @see {@link layerSessionStorage} for synchronous tab-session Web Storage
102
+ *
103
+ * @category layers
104
+ * @since 4.0.0
105
+ */
106
+ export const layerIndexedDb = (options?: {
107
+ readonly database?: string | undefined
108
+ }): Layer.Layer<KeyValueStore.KeyValueStore, never, IndexedDb> =>
109
+ Layer.effect(KeyValueStore.KeyValueStore)(
110
+ Effect.gen(function*() {
111
+ const db = yield* Effect.acquireRelease(
112
+ openDatabase(options?.database ?? "effect_key_value_store"),
113
+ (db) => Effect.sync(() => db.close())
114
+ ).pipe(Effect.orDie)
115
+
116
+ return KeyValueStore.make({
117
+ clear: Effect.suspend(() => {
118
+ const store = getKvsEntriesStore(db, "readwrite")
119
+ return idbRequest({ method: "clear", message: "Failed to clear backing store" }, () => store.clear())
120
+ }),
121
+ get: (key: string) =>
122
+ Effect.map(
123
+ Effect.suspend(() => {
124
+ const store = getKvsEntriesStore(db, "readonly")
125
+ return idbRequest<{ key: string; value: string } | undefined>({
126
+ method: "get",
127
+ message: "Failed to get value from backing store",
128
+ key
129
+ }, () => store.get(key))
130
+ }),
131
+ (found) => typeof found?.value === "string" ? found.value : undefined
132
+ ),
133
+ getUint8Array: (key: string) =>
134
+ Effect.map(
135
+ Effect.suspend(() => {
136
+ const store = getKvsEntriesStore(db, "readonly")
137
+ return idbRequest<{ key: string; value: Uint8Array } | undefined>({
138
+ method: "getUint8Array",
139
+ message: "Failed to get value from backing store",
140
+ key
141
+ }, () => store.get(key))
142
+ }),
143
+ (found) => found?.value && found.value instanceof Uint8Array ? found.value : undefined
144
+ ),
145
+ set: (key: string, value: string | Uint8Array) =>
146
+ Effect.asVoid(Effect.suspend(() => {
147
+ const store = getKvsEntriesStore(db, "readwrite")
148
+ return idbRequest(
149
+ { method: "set", message: "Failed to set value in backing store", key },
150
+ () => store.put({ key, value })
151
+ )
152
+ })),
153
+ size: Effect.suspend(() => {
154
+ const store = getKvsEntriesStore(db, "readonly")
155
+ return idbRequest<number>(
156
+ { method: "size", message: "Failed to get backing store size" },
157
+ () => store.count()
158
+ )
159
+ }),
160
+ remove: (key: string) =>
161
+ Effect.asVoid(Effect.suspend(() => {
162
+ const store = getKvsEntriesStore(db, "readwrite")
163
+ return idbRequest(
164
+ { method: "remove", message: "Failed to remove value from backing store", key },
165
+ () => store.delete(key)
166
+ )
167
+ }))
168
+ })
169
+ })
170
+ )
171
+
172
+ const databaseVersion = 1
173
+ const entriesStoreName = "entries"
174
+ const openDatabase = Effect.fnUntraced(function*(database: string) {
175
+ const idb = (yield* IndexedDb).indexedDB
176
+ const openRequest = yield* Effect.try({
177
+ try: () => idb.open(database, databaseVersion),
178
+ catch: (cause) =>
179
+ new KeyValueStore.KeyValueStoreError({
180
+ method: "open",
181
+ message: "Failed to open backing store database",
182
+ cause
183
+ })
184
+ })
185
+ openRequest.onupgradeneeded = () => {
186
+ const db = openRequest.result
187
+ if (!db.objectStoreNames.contains(entriesStoreName)) {
188
+ db.createObjectStore(entriesStoreName, { keyPath: "key" })
189
+ }
190
+ }
191
+ return yield* idbRequest({ method: "open", message: "Failed to open backing store database" }, () => openRequest)
192
+ })
193
+
194
+ const idbRequest = <A>(
195
+ failArgs: { method: string; message: string; key?: string },
196
+ evaluate: () => IDBRequest<A>
197
+ ): Effect.Effect<A, KeyValueStore.KeyValueStoreError> =>
198
+ Effect.callback<A, KeyValueStore.KeyValueStoreError>((resume) => {
199
+ const request = evaluate()
200
+ if (request.readyState === "done") {
201
+ return resume(Effect.succeed(request.result))
202
+ }
203
+ request.onsuccess = () => {
204
+ resume(Effect.succeed(request.result))
205
+ }
206
+ request.onerror = () =>
207
+ resume(Effect.fail(
208
+ new KeyValueStore.KeyValueStoreError({
209
+ ...failArgs,
210
+ cause: request.error
211
+ })
212
+ ))
213
+ })
214
+
215
+ const getKvsEntriesStore = (db: IDBDatabase, mode: IDBTransactionMode) => {
216
+ const transaction = db.transaction(entriesStoreName, mode)
217
+ return transaction.objectStore(entriesStoreName)
218
+ }