@effect/platform-browser 4.0.0-beta.99 → 4.0.0-rc.108

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 (137) hide show
  1. package/AGENTS.md +381 -0
  2. package/CLAUDE.md +381 -0
  3. package/README.md +14 -1
  4. package/ai-docs/README.md +44 -0
  5. package/ai-docs/package.json +36 -0
  6. package/ai-docs/src/01_effect/01_basics/01_effect-gen.ts +30 -0
  7. package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +39 -0
  8. package/ai-docs/src/01_effect/01_basics/10_creating-effects.ts +74 -0
  9. package/ai-docs/src/01_effect/01_basics/index.md +5 -0
  10. package/ai-docs/src/01_effect/02_schema/10_schema-basics.ts +43 -0
  11. package/ai-docs/src/01_effect/02_schema/index.md +7 -0
  12. package/ai-docs/src/01_effect/03_services/01_service.ts +45 -0
  13. package/ai-docs/src/01_effect/03_services/10_reference.ts +10 -0
  14. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +70 -0
  15. package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +66 -0
  16. package/ai-docs/src/01_effect/03_services/index.md +5 -0
  17. package/ai-docs/src/01_effect/04_errors/01_error-handling.ts +30 -0
  18. package/ai-docs/src/01_effect/04_errors/10_catch-tags.ts +24 -0
  19. package/ai-docs/src/01_effect/04_errors/20_reason-errors.ts +64 -0
  20. package/ai-docs/src/01_effect/04_errors/index.md +1 -0
  21. package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +105 -0
  22. package/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts +31 -0
  23. package/ai-docs/src/01_effect/05_resources/30_layer-map.ts +86 -0
  24. package/ai-docs/src/01_effect/05_resources/index.md +3 -0
  25. package/ai-docs/src/01_effect/06_running/10_run-main.ts +30 -0
  26. package/ai-docs/src/01_effect/06_running/20_layer-launch.ts +27 -0
  27. package/ai-docs/src/01_effect/06_running/index.md +1 -0
  28. package/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts +56 -0
  29. package/ai-docs/src/01_effect/07_pubsub/index.md +3 -0
  30. package/ai-docs/src/03_stream/10_creating-streams.ts +103 -0
  31. package/ai-docs/src/03_stream/20_consuming-streams.ts +137 -0
  32. package/ai-docs/src/03_stream/30_encoding.ts +165 -0
  33. package/ai-docs/src/03_stream/index.md +4 -0
  34. package/ai-docs/src/04_integration/10_managed-runtime.ts +129 -0
  35. package/ai-docs/src/04_integration/index.md +5 -0
  36. package/ai-docs/src/05_batching/10_request-resolver.ts +89 -0
  37. package/ai-docs/src/05_batching/index.md +3 -0
  38. package/ai-docs/src/06_schedule/10_schedules.ts +110 -0
  39. package/ai-docs/src/06_schedule/index.md +3 -0
  40. package/ai-docs/src/07_datetime/10_creating-and-formatting.ts +30 -0
  41. package/ai-docs/src/07_datetime/20_time-zones.ts +44 -0
  42. package/ai-docs/src/07_datetime/index.md +5 -0
  43. package/ai-docs/src/08_observability/10_logging.ts +66 -0
  44. package/ai-docs/src/08_observability/20_otlp-tracing.ts +95 -0
  45. package/ai-docs/src/08_observability/index.md +7 -0
  46. package/ai-docs/src/09_testing/10_effect-tests.ts +55 -0
  47. package/ai-docs/src/09_testing/20_layer-tests.ts +138 -0
  48. package/ai-docs/src/09_testing/index.md +1 -0
  49. package/ai-docs/src/10_predicate/01_basics.ts +14 -0
  50. package/ai-docs/src/10_predicate/index.md +9 -0
  51. package/ai-docs/src/50_http-client/10_basics.ts +102 -0
  52. package/ai-docs/src/50_http-client/index.md +3 -0
  53. package/ai-docs/src/51_http-server/10_basics.ts +116 -0
  54. package/ai-docs/src/51_http-server/fixtures/api/Api.ts +14 -0
  55. package/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +36 -0
  56. package/ai-docs/src/51_http-server/fixtures/api/System.ts +10 -0
  57. package/ai-docs/src/51_http-server/fixtures/api/Users.ts +91 -0
  58. package/ai-docs/src/51_http-server/fixtures/domain/User.ts +12 -0
  59. package/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts +22 -0
  60. package/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +36 -0
  61. package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +71 -0
  62. package/ai-docs/src/51_http-server/fixtures/server/Users.ts +62 -0
  63. package/ai-docs/src/51_http-server/index.md +3 -0
  64. package/ai-docs/src/60_child-process/10_working-with-child-processes.ts +117 -0
  65. package/ai-docs/src/60_child-process/index.md +3 -0
  66. package/ai-docs/src/70_cli/10_basics.ts +136 -0
  67. package/ai-docs/src/70_cli/index.md +5 -0
  68. package/ai-docs/src/71_ai/10_language-model.ts +156 -0
  69. package/ai-docs/src/71_ai/20_tools.ts +226 -0
  70. package/ai-docs/src/71_ai/30_chat.ts +158 -0
  71. package/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts +9 -0
  72. package/ai-docs/src/71_ai/index.md +5 -0
  73. package/ai-docs/src/80_cluster/10_entities.ts +97 -0
  74. package/ai-docs/src/80_cluster/index.md +4 -0
  75. package/ai-docs/src/index.md +10 -0
  76. package/ai-docs/tsconfig.json +24 -0
  77. package/dist/BrowserCrypto.d.ts +1 -1
  78. package/dist/BrowserCrypto.d.ts.map +1 -1
  79. package/dist/BrowserCrypto.js +4 -2
  80. package/dist/BrowserCrypto.js.map +1 -1
  81. package/dist/BrowserHttpClient.d.ts +5 -5
  82. package/dist/BrowserHttpClient.d.ts.map +1 -1
  83. package/dist/BrowserHttpClient.js +12 -7
  84. package/dist/BrowserHttpClient.js.map +1 -1
  85. package/dist/BrowserKeyValueStore.d.ts.map +1 -1
  86. package/dist/BrowserKeyValueStore.js +38 -9
  87. package/dist/BrowserKeyValueStore.js.map +1 -1
  88. package/dist/BrowserPersistence.js.map +1 -1
  89. package/dist/BrowserRuntime.d.ts +3 -3
  90. package/dist/BrowserRuntime.js +1 -1
  91. package/dist/BrowserRuntime.js.map +1 -1
  92. package/dist/BrowserSocket.js.map +1 -1
  93. package/dist/BrowserStream.d.ts +2 -2
  94. package/dist/BrowserStream.js +2 -2
  95. package/dist/BrowserStream.js.map +1 -1
  96. package/dist/BrowserWorker.js.map +1 -1
  97. package/dist/BrowserWorkerRunner.d.ts.map +1 -1
  98. package/dist/BrowserWorkerRunner.js +2 -1
  99. package/dist/BrowserWorkerRunner.js.map +1 -1
  100. package/dist/Clipboard.d.ts +1 -1
  101. package/dist/Clipboard.js.map +1 -1
  102. package/dist/Geolocation.d.ts +1 -1
  103. package/dist/Geolocation.js.map +1 -1
  104. package/dist/IndexedDb.d.ts +3 -3
  105. package/dist/IndexedDb.d.ts.map +1 -1
  106. package/dist/IndexedDb.js +2 -2
  107. package/dist/IndexedDb.js.map +1 -1
  108. package/dist/IndexedDbDatabase.d.ts +3 -3
  109. package/dist/IndexedDbDatabase.d.ts.map +1 -1
  110. package/dist/IndexedDbDatabase.js +13 -7
  111. package/dist/IndexedDbDatabase.js.map +1 -1
  112. package/dist/IndexedDbQueryBuilder.d.ts +9 -9
  113. package/dist/IndexedDbQueryBuilder.d.ts.map +1 -1
  114. package/dist/IndexedDbQueryBuilder.js +38 -13
  115. package/dist/IndexedDbQueryBuilder.js.map +1 -1
  116. package/dist/IndexedDbTable.d.ts +9 -9
  117. package/dist/IndexedDbTable.js.map +1 -1
  118. package/dist/IndexedDbVersion.d.ts +4 -4
  119. package/dist/IndexedDbVersion.js.map +1 -1
  120. package/dist/Permissions.d.ts +1 -1
  121. package/dist/Permissions.js.map +1 -1
  122. package/dist/index.js.map +1 -1
  123. package/package.json +10 -11
  124. package/src/BrowserCrypto.ts +4 -2
  125. package/src/BrowserHttpClient.ts +11 -7
  126. package/src/BrowserKeyValueStore.ts +50 -8
  127. package/src/BrowserRuntime.ts +3 -3
  128. package/src/BrowserStream.ts +2 -2
  129. package/src/BrowserWorkerRunner.ts +2 -1
  130. package/src/Clipboard.ts +1 -1
  131. package/src/Geolocation.ts +1 -1
  132. package/src/IndexedDb.ts +3 -3
  133. package/src/IndexedDbDatabase.ts +15 -9
  134. package/src/IndexedDbQueryBuilder.ts +64 -21
  135. package/src/IndexedDbTable.ts +9 -9
  136. package/src/IndexedDbVersion.ts +4 -4
  137. package/src/Permissions.ts +1 -1
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@effect/platform-browser",
3
3
  "type": "module",
4
- "version": "4.0.0-beta.99",
4
+ "version": "4.0.0-rc.108",
5
5
  "license": "MIT",
6
6
  "description": "Platform specific implementations for the browser",
7
7
  "homepage": "https://effect.website",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Effect-TS/effect.git",
11
- "directory": "packages/platform-browser"
11
+ "directory": "packages/platform/browser"
12
12
  },
13
13
  "bugs": {
14
14
  "url": "https://github.com/Effect-TS/effect/issues"
@@ -31,6 +31,7 @@
31
31
  ".": "./dist/index.js",
32
32
  "./*": "./dist/*.js",
33
33
  "./internal/*": null,
34
+ "./index": null,
34
35
  "./*/index": null
35
36
  },
36
37
  "files": [
@@ -38,29 +39,27 @@
38
39
  "dist/**/*.js",
39
40
  "dist/**/*.js.map",
40
41
  "dist/**/*.d.ts",
41
- "dist/**/*.d.ts.map"
42
+ "dist/**/*.d.ts.map",
43
+ "AGENTS.md",
44
+ "CLAUDE.md",
45
+ "ai-docs/**/*"
42
46
  ],
43
47
  "publishConfig": {
44
48
  "access": "public",
45
49
  "provenance": true
46
50
  },
47
51
  "peerDependencies": {
48
- "effect": "^4.0.0-beta.99"
52
+ "effect": "^4.0.0-rc.108"
49
53
  },
50
54
  "devDependencies": {
51
55
  "fake-indexeddb": "^6.2.5",
52
56
  "mock-xmlhttprequest": "^8.4.1",
53
- "effect": "^4.0.0-beta.99"
54
- },
55
- "dependencies": {
56
- "multipasta": "^0.2.8"
57
+ "effect": "^4.0.0-rc.108"
57
58
  },
58
59
  "scripts": {
59
60
  "codegen": "effect-utils codegen",
60
61
  "build": "tsc -b tsconfig.json && pnpm babel",
61
62
  "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps",
62
- "check": "tsc -b tsconfig.json",
63
- "test": "vitest",
64
- "coverage": "vitest --coverage"
63
+ "check": "tsc -b tsconfig.json"
65
64
  }
66
65
  }
@@ -22,7 +22,7 @@ import * as PlatformError from "effect/PlatformError"
22
22
  * Use to override the browser `Crypto` object used by the platform crypto
23
23
  * layer.
24
24
  *
25
- * @category references
25
+ * @category services
26
26
  * @since 1.0.0
27
27
  */
28
28
  export const WebCrypto = Context.Reference<Crypto>("@effect/platform-browser/Crypto/WebCrypto", {
@@ -60,7 +60,9 @@ export const layer: Layer.Layer<EffectCrypto.Crypto> = Layer.effect(
60
60
  }
61
61
  const randomBytes = (size: number): Uint8Array => {
62
62
  const bytes = new Uint8Array(size)
63
- crypto.getRandomValues(bytes)
63
+ for (let i = 0; i < bytes.length; i += 65_536) {
64
+ crypto.getRandomValues(bytes.subarray(i, i + 65_536))
65
+ }
64
66
  return bytes
65
67
  }
66
68
 
@@ -28,8 +28,8 @@ import * as HttpClientError from "effect/unstable/http/HttpClientError"
28
28
  import type * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
29
29
  import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
30
30
  import * as HttpIncomingMessage from "effect/unstable/http/HttpIncomingMessage"
31
+ import * as HeaderParser from "effect/unstable/http/MultipartParser/HeadersParser"
31
32
  import * as UrlParams from "effect/unstable/http/UrlParams"
32
- import * as HeaderParser from "multipasta/HeadersParser"
33
33
 
34
34
  // =============================================================================
35
35
  // Fetch
@@ -39,14 +39,14 @@ export {
39
39
  /**
40
40
  * Context reference for the `fetch` implementation used by the fetch-based HTTP client.
41
41
  *
42
- * @category fetch
42
+ * @category services
43
43
  * @since 4.0.0
44
44
  */
45
45
  Fetch,
46
46
  /**
47
47
  * Layer that provides an `HttpClient` implementation backed by the configured `Fetch` function.
48
48
  *
49
- * @category fetch
49
+ * @category layers
50
50
  * @since 4.0.0
51
51
  */
52
52
  layer as layerFetch,
@@ -58,7 +58,7 @@ export {
58
58
  * Use to provide default credentials, cache, redirect, integrity, or other
59
59
  * fetch options for browser HTTP requests.
60
60
  *
61
- * @category fetch
61
+ * @category services
62
62
  * @since 4.0.0
63
63
  */
64
64
  RequestInit
@@ -87,7 +87,7 @@ export type XHRResponseType = "arraybuffer" | "text"
87
87
  * @see {@link XHRResponseType} for the allowed response body modes
88
88
  * @see {@link withXHRArrayBuffer} for scoping XHR response handling to `ArrayBuffer`
89
89
  *
90
- * @category references
90
+ * @category services
91
91
  * @since 4.0.0
92
92
  */
93
93
  export const CurrentXHRResponseType: Context.Reference<XHRResponseType> = Context.Reference(
@@ -98,7 +98,7 @@ export const CurrentXHRResponseType: Context.Reference<XHRResponseType> = Contex
98
98
  /**
99
99
  * Runs an effect with `CurrentXHRResponseType` set to `"arraybuffer"` so the XHR HTTP client receives response bodies as `ArrayBuffer` values.
100
100
  *
101
- * @category references
101
+ * @category providing services
102
102
  * @since 4.0.0
103
103
  */
104
104
  export const withXHRArrayBuffer = <A, E, R>(
@@ -399,7 +399,11 @@ class ClientResponseImpl extends IncomingMessageImpl<HttpClientError.HttpClientE
399
399
  }
400
400
 
401
401
  get formData(): Effect.Effect<FormData, HttpClientError.HttpClientError> {
402
- return Effect.die("Not implemented")
402
+ return Effect.flatMap(this.arrayBuffer, (body) =>
403
+ Effect.tryPromise({
404
+ try: () => new globalThis.Response(body, { headers: this.headers }).formData(),
405
+ catch: this.onError
406
+ }))
403
407
  }
404
408
 
405
409
  override toString(): string {
@@ -74,8 +74,11 @@ export const layerIndexedDb = (options?: {
74
74
 
75
75
  return KeyValueStore.make({
76
76
  clear: Effect.suspend(() => {
77
- const store = getKvsEntriesStore(db, "readwrite")
78
- return idbRequest({ method: "clear", message: "Failed to clear backing store" }, () => store.clear())
77
+ return idbWriteRequest(
78
+ db,
79
+ { method: "clear", message: "Failed to clear backing store" },
80
+ (store) => store.clear()
81
+ )
79
82
  }),
80
83
  get: (key: string) =>
81
84
  Effect.map(
@@ -103,10 +106,10 @@ export const layerIndexedDb = (options?: {
103
106
  ),
104
107
  set: (key: string, value: string | Uint8Array) =>
105
108
  Effect.asVoid(Effect.suspend(() => {
106
- const store = getKvsEntriesStore(db, "readwrite")
107
- return idbRequest(
109
+ return idbWriteRequest(
110
+ db,
108
111
  { method: "set", message: "Failed to set value in backing store", key },
109
- () => store.put({ key, value })
112
+ (store) => store.put({ key, value })
110
113
  )
111
114
  })),
112
115
  size: Effect.suspend(() => {
@@ -118,10 +121,10 @@ export const layerIndexedDb = (options?: {
118
121
  }),
119
122
  remove: (key: string) =>
120
123
  Effect.asVoid(Effect.suspend(() => {
121
- const store = getKvsEntriesStore(db, "readwrite")
122
- return idbRequest(
124
+ return idbWriteRequest(
125
+ db,
123
126
  { method: "remove", message: "Failed to remove value from backing store", key },
124
- () => store.delete(key)
127
+ (store) => store.delete(key)
125
128
  )
126
129
  }))
127
130
  })
@@ -171,6 +174,45 @@ const idbRequest = <A>(
171
174
  ))
172
175
  })
173
176
 
177
+ const idbWriteRequest = <A>(
178
+ db: IDBDatabase,
179
+ failArgs: { method: string; message: string; key?: string },
180
+ evaluate: (store: IDBObjectStore) => IDBRequest<A>
181
+ ): Effect.Effect<A, KeyValueStore.KeyValueStoreError> =>
182
+ Effect.callback<A, KeyValueStore.KeyValueStoreError>((resume) => {
183
+ const transaction = db.transaction(entriesStoreName, "readwrite")
184
+ const request = evaluate(transaction.objectStore(entriesStoreName))
185
+ let result: A
186
+ let done = false
187
+
188
+ const fail = (cause: unknown) => {
189
+ if (done) return
190
+ done = true
191
+ resume(Effect.fail(new KeyValueStore.KeyValueStoreError({ ...failArgs, cause })))
192
+ }
193
+
194
+ if (request.readyState === "done") {
195
+ result = request.result
196
+ } else {
197
+ request.onsuccess = () => {
198
+ result = request.result
199
+ }
200
+ request.onerror = () => fail(request.error)
201
+ }
202
+
203
+ transaction.oncomplete = () => {
204
+ if (done) return
205
+ done = true
206
+ resume(Effect.succeed(result!))
207
+ }
208
+ transaction.onerror = () => fail(transaction.error)
209
+ transaction.onabort = () => fail(transaction.error)
210
+
211
+ return Effect.sync(() => {
212
+ if (!done) transaction.abort()
213
+ })
214
+ })
215
+
174
216
  const getKvsEntriesStore = (db: IDBDatabase, mode: IDBTransactionMode) => {
175
217
  const transaction = db.transaction(entriesStoreName, mode)
176
218
  return transaction.objectStore(entriesStoreName)
@@ -29,7 +29,7 @@ import { makeRunMain, type Teardown } from "effect/Runtime"
29
29
  * The `beforeunload` interruption is best-effort. Browser teardown may prevent
30
30
  * asynchronous finalizers, network work, timers, or prompts from completing.
31
31
  *
32
- * @category Runtime
32
+ * @category running
33
33
  * @since 4.0.0
34
34
  */
35
35
  export const runMain: {
@@ -52,7 +52,7 @@ export const runMain: {
52
52
  * The `beforeunload` interruption is best-effort. Browser teardown may prevent
53
53
  * asynchronous finalizers, network work, timers, or prompts from completing.
54
54
  *
55
- * @category Runtime
55
+ * @category running
56
56
  * @since 4.0.0
57
57
  */
58
58
  (
@@ -80,7 +80,7 @@ export const runMain: {
80
80
  * The `beforeunload` interruption is best-effort. Browser teardown may prevent
81
81
  * asynchronous finalizers, network work, timers, or prompts from completing.
82
82
  *
83
- * @category Runtime
83
+ * @category running
84
84
  * @since 4.0.0
85
85
  */
86
86
  <E, A>(
@@ -19,7 +19,7 @@ import * as Stream from "effect/Stream"
19
19
  * buffer size by passing an object as the second argument with the `bufferSize`
20
20
  * field.
21
21
  *
22
- * @category streams
22
+ * @category constructors
23
23
  * @since 4.0.0
24
24
  */
25
25
  export const fromEventListenerWindow = <K extends keyof WindowEventMap>(
@@ -41,7 +41,7 @@ export const fromEventListenerWindow = <K extends keyof WindowEventMap>(
41
41
  * buffer size by passing an object as the second argument with the `bufferSize`
42
42
  * field.
43
43
  *
44
- * @category streams
44
+ * @category constructors
45
45
  * @since 4.0.0
46
46
  */
47
47
  export const fromEventListenerDocument = <K extends keyof DocumentEventMap>(
@@ -81,6 +81,7 @@ export const make = (self: MessagePort | Window): WorkerRunner.WorkerRunnerPlatf
81
81
  return Deferred.doneUnsafe(closeLatch, Exit.void)
82
82
  }
83
83
  ports.delete(portId)
84
+ Queue.offerUnsafe(disconnects, portId)
84
85
  Effect.runFork(Scope.close(port[1], Exit.void))
85
86
  }
86
87
  }
@@ -122,7 +123,7 @@ export const make = (self: MessagePort | Window): WorkerRunner.WorkerRunnerPlatf
122
123
  portScope,
123
124
  Effect.sync(() => {
124
125
  port.removeEventListener("message", onMsg)
125
- port.removeEventListener("messageerror", onError)
126
+ port.removeEventListener("messageerror", onMessageError)
126
127
  port.close()
127
128
  })
128
129
  ))
package/src/Clipboard.ts CHANGED
@@ -38,7 +38,7 @@ const ErrorTypeId = "~@effect/platform-browser/Clipboard/ClipboardError"
38
38
  * MIME type support varies by browser. Failed browser operations are surfaced
39
39
  * as `ClipboardError`.
40
40
  *
41
- * @category models
41
+ * @category services
42
42
  * @since 4.0.0
43
43
  */
44
44
  export interface Clipboard {
@@ -44,7 +44,7 @@ const ErrorTypeId = "~@effect/platform-browser/Geolocation/GeolocationError"
44
44
  * @see {@link GeolocationError} for represented browser geolocation failures
45
45
  * @see {@link layer} for the browser-backed service implementation
46
46
  *
47
- * @category models
47
+ * @category services
48
48
  * @since 4.0.0
49
49
  */
50
50
  export interface Geolocation {
package/src/IndexedDb.ts CHANGED
@@ -19,7 +19,7 @@ const TypeId = "~@effect/platform-browser/IndexedDb"
19
19
  /**
20
20
  * Service interface that provides the browser `indexedDB` factory and `IDBKeyRange` constructor.
21
21
  *
22
- * @category models
22
+ * @category services
23
23
  * @since 4.0.0
24
24
  */
25
25
  export interface IndexedDb {
@@ -40,7 +40,7 @@ export const IndexedDb: Context.Service<IndexedDb, IndexedDb> = Context.Service<
40
40
  const IDBFlatKey = Schema.Union([
41
41
  Schema.String,
42
42
  Schema.Number.check(Schema.makeFilter((input) => !Number.isNaN(input))),
43
- Schema.DateValid,
43
+ Schema.Date,
44
44
  Schema.declare(
45
45
  (input): input is BufferSource =>
46
46
  input instanceof ArrayBuffer ||
@@ -93,7 +93,7 @@ export const make = (impl: Omit<IndexedDb, typeof TypeId>): IndexedDb => Indexed
93
93
  /**
94
94
  * Layer that provides `IndexedDb` from `window.indexedDB` and `window.IDBKeyRange`, failing with a config error when they are unavailable.
95
95
  *
96
- * @category constructors
96
+ * @category layers
97
97
  * @since 4.0.0
98
98
  */
99
99
  export const layerWindow: Layer.Layer<IndexedDb> = Layer.effect(
@@ -31,12 +31,6 @@ const SchemaProto = {
31
31
  [TypeId]: {
32
32
  _A: (_: never) => _
33
33
  },
34
- ...Effectable.Prototype<IndexedDbSchema<any, any, any>>({
35
- label: "IndexedDbSchema",
36
- evaluate() {
37
- return this.getQueryBuilder
38
- }
39
- }),
40
34
  get getQueryBuilder() {
41
35
  const self = this as unknown as IndexedDbSchema<any, any, any>
42
36
  return IndexedDbDatabase.useSync(({ database, IDBKeyRange, reactivity }) =>
@@ -48,6 +42,12 @@ const SchemaProto = {
48
42
  })
49
43
  )
50
44
  },
45
+ ...Effectable.Prototype<IndexedDbSchema<any, any, any>>({
46
+ label: "IndexedDbSchema",
47
+ evaluate() {
48
+ return this.getQueryBuilder
49
+ }
50
+ }),
51
51
  add<Version extends IndexedDbVersion.AnyWithProps>(
52
52
  this: IndexedDbSchema<any, any, any>,
53
53
  version: Version,
@@ -126,7 +126,7 @@ export class IndexedDbDatabaseError extends Data.TaggedError(
126
126
  * @see {@link IndexedDb.IndexedDb} for the lower-level browser IndexedDB primitives
127
127
  * @see {@link make} for creating a schema that provides this service as a layer
128
128
  *
129
- * @category models
129
+ * @category services
130
130
  * @since 4.0.0
131
131
  */
132
132
  export class IndexedDbDatabase extends Context.Service<
@@ -234,7 +234,7 @@ export interface Transaction<
234
234
  /**
235
235
  * Extracts the string-literal index names defined by an `IndexedDbTable`.
236
236
  *
237
- * @category models
237
+ * @category utility types
238
238
  * @since 4.0.0
239
239
  */
240
240
  export type IndexFromTable<Table extends IndexedDbTable.AnyWithProps> = IsStringLiteral<
@@ -245,7 +245,7 @@ export type IndexFromTable<Table extends IndexedDbTable.AnyWithProps> = IsString
245
245
  /**
246
246
  * Extracts the valid index names for a table name within an IndexedDB version.
247
247
  *
248
- * @category models
248
+ * @category utility types
249
249
  * @since 4.0.0
250
250
  */
251
251
  export type IndexFromTableName<
@@ -478,6 +478,12 @@ const layer = <DatabaseName extends string>(
478
478
  Effect.provideService(IndexedDbQueryBuilder.IndexedDbTransaction, transaction)
479
479
  )
480
480
  fiber = runForkWith(effect)
481
+ fiber.addObserver((exit) => {
482
+ if (exit._tag === "Failure") {
483
+ transaction.abort()
484
+ resume(exit)
485
+ }
486
+ })
481
487
  fiber.currentDispatcher.flush()
482
488
  }
483
489
 
@@ -136,13 +136,17 @@ export interface IndexedDbQueryBuilder<
136
136
  readonly tables: Tables
137
137
  readonly mode: Mode
138
138
  readonly durability?: IDBTransactionDurability
139
- }) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, IndexedDbTransaction>>
139
+ }) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<
140
+ A,
141
+ E | IndexedDbQueryError,
142
+ Exclude<R, IndexedDbTransaction>
143
+ >
140
144
  }
141
145
 
142
146
  /**
143
147
  * Valid key-path type for a table schema, using encoded fields whose values are IndexedDB-valid keys.
144
148
  *
145
- * @category models
149
+ * @category utility types
146
150
  * @since 4.0.0
147
151
  */
148
152
  export type KeyPath<TableSchema extends IndexedDbTable.AnySchemaStruct> =
@@ -152,7 +156,7 @@ export type KeyPath<TableSchema extends IndexedDbTable.AnySchemaStruct> =
152
156
  /**
153
157
  * Valid numeric key-path type for a table schema, used for auto-increment key paths.
154
158
  *
155
- * @category models
159
+ * @category utility types
156
160
  * @since 4.0.0
157
161
  */
158
162
  export type KeyPathNumber<TableSchema extends IndexedDbTable.AnySchemaStruct> =
@@ -168,7 +172,7 @@ export declare namespace IndexedDbQuery {
168
172
  /**
169
173
  * Decoded row type returned by select queries, adding a `key` field when the table does not define a key path.
170
174
  *
171
- * @category models
175
+ * @category utility types
172
176
  * @since 4.0.0
173
177
  */
174
178
  export type SelectType<
@@ -181,7 +185,7 @@ export declare namespace IndexedDbQuery {
181
185
  /**
182
186
  * Input type for insert and upsert operations, adjusted for auto-increment keys and out-of-line keys.
183
187
  *
184
- * @category models
188
+ * @category utility types
185
189
  * @since 4.0.0
186
190
  */
187
191
  export type ModifyType<
@@ -215,7 +219,7 @@ export declare namespace IndexedDbQuery {
215
219
  /**
216
220
  * Value type accepted by `equals` comparisons for a table key path or index.
217
221
  *
218
- * @category models
222
+ * @category utility types
219
223
  * @since 4.0.0
220
224
  */
221
225
  export type EqualsType<
@@ -229,7 +233,7 @@ export declare namespace IndexedDbQuery {
229
233
  /**
230
234
  * Value type accepted by range comparisons for a table key path or index, including partial tuples for compound indexes.
231
235
  *
232
- * @category models
236
+ * @category utility types
233
237
  * @since 4.0.0
234
238
  */
235
239
  export type ExtractIndexType<
@@ -248,7 +252,7 @@ export declare namespace IndexedDbQuery {
248
252
  /**
249
253
  * Mutation input type for insert and upsert operations, including any required key fields.
250
254
  *
251
- * @category models
255
+ * @category utility types
252
256
  * @since 4.0.0
253
257
  */
254
258
  export type ModifyWithKey<Table extends IndexedDbTable.AnyWithProps> = ModifyType<Table>
@@ -719,7 +723,7 @@ export declare namespace IndexedDbQuery {
719
723
  /**
720
724
  * Service tag for the active `IDBTransaction` used to share a transaction across IndexedDB query effects.
721
725
  *
722
- * @category models
726
+ * @category services
723
727
  * @since 4.0.0
724
728
  */
725
729
  export class IndexedDbTransaction extends Context.Service<IndexedDbTransaction, globalThis.IDBTransaction>()(
@@ -752,6 +756,7 @@ const applyDelete = (query: IndexedDbQuery.Delete<any, never>) =>
752
756
  durability: query.delete.from.table.durability
753
757
  })
754
758
  const objectStore = transaction.objectStore(query.delete.from.table.tableName)
759
+ const store = query.delete.index === undefined ? objectStore : objectStore.index(query.delete.index)
755
760
  const predicate = query.predicate
756
761
 
757
762
  let keyRange: globalThis.IDBKeyRange | undefined = undefined
@@ -782,8 +787,8 @@ const applyDelete = (query: IndexedDbQuery.Delete<any, never>) =>
782
787
 
783
788
  let request: globalThis.IDBRequest
784
789
 
785
- if (query.limitValue !== undefined || predicate) {
786
- const cursorRequest = objectStore.openCursor()
790
+ if (query.delete.index !== undefined || query.limitValue !== undefined || predicate) {
791
+ const cursorRequest = store.openCursor(keyRange)
787
792
  let count = 0
788
793
 
789
794
  cursorRequest.onerror = () => {
@@ -897,7 +902,7 @@ const applySelect = Effect.fnUntraced(function*(
897
902
  const keyPath = query.from.table.keyPath
898
903
  const predicate = query.predicate
899
904
 
900
- const data = predicate || keyPath === undefined || query.offsetValue !== undefined ?
905
+ const data = predicate || keyPath === undefined || query.offsetValue !== undefined || query.reverseValue ?
901
906
  yield* Effect.callback<Array<any>, IndexedDbQueryError>((resume) => {
902
907
  const { keyRange, store } = getReadonlyObjectStore(query)
903
908
 
@@ -1001,7 +1006,13 @@ const applyFirst = Effect.fnUntraced(function*(
1001
1006
  }
1002
1007
 
1003
1008
  request.onsuccess = () => {
1004
- resume(Effect.succeed(request.result))
1009
+ if (request.result === undefined) {
1010
+ resume(
1011
+ Effect.fail(new Cause.NoSuchElementError(`No such element in table ${query.select.from.table.tableName}`))
1012
+ )
1013
+ } else {
1014
+ resume(Effect.succeed(request.result))
1015
+ }
1005
1016
  }
1006
1017
  } else {
1007
1018
  const request = store.openCursor()
@@ -1331,7 +1342,6 @@ const FromProto: Omit<
1331
1342
  | "countCache"
1332
1343
  | "deleteCache"
1333
1344
  > = {
1334
- ...CommonProto,
1335
1345
  select<Index extends IndexedDbDatabase.IndexFromTable<any>>(
1336
1346
  this: IndexedDbQuery.From<any>,
1337
1347
  index?: Index
@@ -1392,7 +1402,8 @@ const FromProto: Omit<
1392
1402
  database: self.database.current,
1393
1403
  table: self.table
1394
1404
  })
1395
- }
1405
+ },
1406
+ ...CommonProto
1396
1407
  }
1397
1408
 
1398
1409
  const makeFrom = <
@@ -1516,7 +1527,7 @@ const DeleteProto: Omit<
1516
1527
  filter(this: IndexedDbQuery.Delete<any, never>, filter: (value: IndexedDbTable.Encoded<any>) => boolean) {
1517
1528
  const prev = this.predicate
1518
1529
  return makeDelete({
1519
- delete: this.delete,
1530
+ ...this,
1520
1531
  predicate: prev ? (item) => prev(item) && filter(item) : filter
1521
1532
  })
1522
1533
  },
@@ -1760,18 +1771,22 @@ const SelectProto: Omit<
1760
1771
  }) {
1761
1772
  const limit = this.limitValue
1762
1773
  const chunkSize = Math.min(options?.chunkSize ?? 100, limit ?? Number.MAX_SAFE_INTEGER)
1763
- const initial = this.limit(chunkSize)
1774
+ const initialOffset = this.offsetValue ?? 0
1764
1775
  return Stream.suspend(() => {
1765
1776
  let total = 0
1777
+ const initial = this.limit(chunkSize)
1766
1778
  return Stream.paginate(initial, (select) =>
1767
1779
  Effect.map(
1768
1780
  applySelect(select as any),
1769
1781
  (data) => {
1770
1782
  total += data.length
1771
- ;(select as any).offsetValue = total
1772
1783
  const reachedLimit = limit && total >= limit
1773
1784
  const isPartial = data.length < chunkSize
1774
- return [data, isPartial || reachedLimit ? Option.none() : Option.some(select)] as const
1785
+ const next = makeSelect({
1786
+ ...select,
1787
+ offsetValue: initialOffset + total
1788
+ })
1789
+ return [data, isPartial || reachedLimit ? Option.none() : Option.some(next)] as const
1775
1790
  }
1776
1791
  ))
1777
1792
  })
@@ -1951,7 +1966,6 @@ const QueryBuilderProto: Omit<
1951
1966
  | "IDBTransaction"
1952
1967
  | "reactivity"
1953
1968
  > = {
1954
- ...CommonProto,
1955
1969
  use(this: IndexedDbQueryBuilder<any>, f: (database: globalThis.IDBDatabase) => any) {
1956
1970
  return Effect.try({
1957
1971
  try: () => f(this.database.current),
@@ -1979,6 +1993,7 @@ const QueryBuilderProto: Omit<
1979
1993
  const self = this as IndexedDbQueryBuilder<any>
1980
1994
  return applyClearAll({ database: self.database.current })
1981
1995
  },
1996
+ ...CommonProto,
1982
1997
  withTransaction(this: IndexedDbQueryBuilder<any>, options: {
1983
1998
  readonly tables: NonEmptyReadonlyArray<any>
1984
1999
  readonly mode: globalThis.IDBTransactionMode
@@ -1987,7 +2002,11 @@ const QueryBuilderProto: Omit<
1987
2002
  return (effect) =>
1988
2003
  Effect.suspend(() => {
1989
2004
  const transaction = this.database.current.transaction(options.tables, options.mode, options)
1990
- return Effect.provideService(effect, IndexedDbTransaction, transaction)
2005
+ return Effect.provideService(effect, IndexedDbTransaction, transaction).pipe(
2006
+ Effect.onExit((exit) =>
2007
+ exit._tag === "Success" ? awaitTransaction(transaction) : abortTransaction(transaction)
2008
+ )
2009
+ )
1991
2010
  }).pipe(
1992
2011
  // To prevent async gaps between transaction queries
1993
2012
  Effect.provideService(References.PreventSchedulerYield, true)
@@ -1995,6 +2014,30 @@ const QueryBuilderProto: Omit<
1995
2014
  }
1996
2015
  }
1997
2016
 
2017
+ const abortTransaction = (transaction: globalThis.IDBTransaction) =>
2018
+ Effect.try({
2019
+ try: () => transaction.abort(),
2020
+ catch: () => undefined
2021
+ }).pipe(Effect.ignore)
2022
+
2023
+ const awaitTransaction = (transaction: globalThis.IDBTransaction) =>
2024
+ Effect.callback<void, IndexedDbQueryError>((resume) => {
2025
+ transaction.oncomplete = () => {
2026
+ resume(Effect.void)
2027
+ }
2028
+ transaction.onabort = () => {
2029
+ resume(
2030
+ Effect.fail(
2031
+ new IndexedDbQueryError({
2032
+ reason: "TransactionError",
2033
+ cause: transaction.error
2034
+ })
2035
+ )
2036
+ )
2037
+ }
2038
+ return abortTransaction(transaction)
2039
+ })
2040
+
1998
2041
  /**
1999
2042
  * Creates an `IndexedDbQueryBuilder` from an open database reference, key-range constructor, table map, and reactivity service.
2000
2043
  *