@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.
- package/dist/BrowserCrypto.d.ts +81 -0
- package/dist/BrowserCrypto.d.ts.map +1 -0
- package/dist/BrowserCrypto.js +119 -0
- package/dist/BrowserCrypto.js.map +1 -0
- package/dist/BrowserHttpClient.d.ts +40 -16
- package/dist/BrowserHttpClient.d.ts.map +1 -1
- package/dist/BrowserHttpClient.js +74 -18
- package/dist/BrowserHttpClient.js.map +1 -1
- package/dist/BrowserKeyValueStore.d.ts +37 -14
- package/dist/BrowserKeyValueStore.d.ts.map +1 -1
- package/dist/BrowserKeyValueStore.js +185 -10
- package/dist/BrowserKeyValueStore.js.map +1 -1
- package/dist/BrowserPersistence.d.ts +40 -0
- package/dist/BrowserPersistence.d.ts.map +1 -0
- package/dist/BrowserPersistence.js +207 -0
- package/dist/BrowserPersistence.js.map +1 -0
- package/dist/BrowserRuntime.d.ts +79 -4
- package/dist/BrowserRuntime.d.ts.map +1 -1
- package/dist/BrowserRuntime.js +19 -1
- package/dist/BrowserRuntime.js.map +1 -1
- package/dist/BrowserSocket.d.ts +64 -6
- package/dist/BrowserSocket.d.ts.map +1 -1
- package/dist/BrowserSocket.js +64 -6
- package/dist/BrowserSocket.js.map +1 -1
- package/dist/BrowserStream.d.ts +48 -5
- package/dist/BrowserStream.d.ts.map +1 -1
- package/dist/BrowserStream.js +48 -5
- package/dist/BrowserStream.js.map +1 -1
- package/dist/BrowserWorker.d.ts +25 -4
- package/dist/BrowserWorker.d.ts.map +1 -1
- package/dist/BrowserWorker.js +62 -6
- package/dist/BrowserWorker.js.map +1 -1
- package/dist/BrowserWorkerRunner.d.ts +31 -6
- package/dist/BrowserWorkerRunner.d.ts.map +1 -1
- package/dist/BrowserWorkerRunner.js +65 -10
- package/dist/BrowserWorkerRunner.js.map +1 -1
- package/dist/Clipboard.d.ts +77 -13
- package/dist/Clipboard.d.ts.map +1 -1
- package/dist/Clipboard.js +57 -11
- package/dist/Clipboard.js.map +1 -1
- package/dist/Geolocation.d.ts +101 -24
- package/dist/Geolocation.d.ts.map +1 -1
- package/dist/Geolocation.js +69 -16
- package/dist/Geolocation.js.map +1 -1
- package/dist/IndexedDb.d.ts +89 -0
- package/dist/IndexedDb.d.ts.map +1 -0
- package/dist/IndexedDb.js +99 -0
- package/dist/IndexedDb.js.map +1 -0
- package/dist/IndexedDbDatabase.d.ts +169 -0
- package/dist/IndexedDbDatabase.d.ts.map +1 -0
- package/dist/IndexedDbDatabase.js +343 -0
- package/dist/IndexedDbDatabase.js.map +1 -0
- package/dist/IndexedDbQueryBuilder.d.ts +438 -0
- package/dist/IndexedDbQueryBuilder.d.ts.map +1 -0
- package/dist/IndexedDbQueryBuilder.js +935 -0
- package/dist/IndexedDbQueryBuilder.js.map +1 -0
- package/dist/IndexedDbTable.d.ts +191 -0
- package/dist/IndexedDbTable.d.ts.map +1 -0
- package/dist/IndexedDbTable.js +96 -0
- package/dist/IndexedDbTable.js.map +1 -0
- package/dist/IndexedDbVersion.d.ts +94 -0
- package/dist/IndexedDbVersion.d.ts.map +1 -0
- package/dist/IndexedDbVersion.js +39 -0
- package/dist/IndexedDbVersion.js.map +1 -0
- package/dist/Permissions.d.ts +76 -16
- package/dist/Permissions.d.ts.map +1 -1
- package/dist/Permissions.js +66 -11
- package/dist/Permissions.js.map +1 -1
- package/dist/index.d.ts +38 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -10
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/BrowserCrypto.ts +129 -0
- package/src/BrowserHttpClient.ts +84 -24
- package/src/BrowserKeyValueStore.ts +201 -12
- package/src/BrowserPersistence.ts +376 -0
- package/src/BrowserRuntime.ts +79 -4
- package/src/BrowserSocket.ts +64 -6
- package/src/BrowserStream.ts +48 -5
- package/src/BrowserWorker.ts +62 -6
- package/src/BrowserWorkerRunner.ts +65 -10
- package/src/Clipboard.ts +74 -13
- package/src/Geolocation.ts +97 -20
- package/src/IndexedDb.ts +127 -0
- package/src/IndexedDbDatabase.ts +664 -0
- package/src/IndexedDbQueryBuilder.ts +2048 -0
- package/src/IndexedDbTable.ts +285 -0
- package/src/IndexedDbVersion.ts +133 -0
- package/src/Permissions.ts +71 -14
- package/src/index.ts +45 -10
package/src/BrowserHttpClient.ts
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
50
|
-
*
|
|
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
|
-
*
|
|
56
|
-
*
|
|
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:
|
|
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
|
-
*
|
|
65
|
-
*
|
|
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
|
-
*
|
|
78
|
-
*
|
|
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
|
|
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 =
|
|
91
|
-
fiber.
|
|
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
|
|
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<
|
|
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)
|
|
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
|
-
*
|
|
381
|
-
*
|
|
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.
|
|
443
|
+
export const layerXMLHttpRequest: Layer.Layer<HttpClient.HttpClient> = HttpClient.layerMergedContext(
|
|
384
444
|
Effect.succeed(makeXmlHttpRequest)
|
|
385
445
|
)
|
|
@@ -1,29 +1,218 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
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`
|
|
61
|
+
* Creates a `KeyValueStore` layer that uses the browser's `localStorage` API and stores values between browser sessions.
|
|
9
62
|
*
|
|
10
|
-
*
|
|
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`
|
|
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
|
-
* @
|
|
25
|
-
* @
|
|
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
|
+
}
|