@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
@@ -0,0 +1,376 @@
1
+ /**
2
+ * IndexedDB-backed persistence layers for browser Effect programs.
3
+ *
4
+ * `BrowserPersistence` connects the unstable persistence services to the
5
+ * browser's IndexedDB storage. Use it for client-side caches that should
6
+ * survive page reloads, offline-capable workflows, remembered query results,
7
+ * and other application-managed values that can be recomputed or invalidated
8
+ * when storage is unavailable.
9
+ *
10
+ * **Mental model**
11
+ *
12
+ * - {@link layerIndexedDb} provides the higher-level `Persistence` service by
13
+ * composing the IndexedDB backing layer with the core persistence layer.
14
+ * - {@link layerBackingIndexedDb} provides only `BackingPersistence` for code
15
+ * that composes persistence services manually.
16
+ * - Entries live in one IndexedDB database, defaulting to
17
+ * `"effect_persistence"`, and one object store keyed by `[storeId, key]`.
18
+ * - The `storeId` namespaces persisted request results; the key identifies one
19
+ * entry inside that store.
20
+ * - Finite TTLs are stored as expiration timestamps and expired entries are
21
+ * removed lazily when they are read.
22
+ *
23
+ * **Common tasks**
24
+ *
25
+ * - Provide browser persistence to `PersistedCache` or other persistence
26
+ * workflows with {@link layerIndexedDb}.
27
+ * - Isolate tests, apps, or migrations by passing a custom `database` option.
28
+ * - Use {@link layerBackingIndexedDb} when another layer should decide how to
29
+ * construct the higher-level `Persistence` service.
30
+ *
31
+ * **Gotchas**
32
+ *
33
+ * - This module requires browser IndexedDB and stores data per origin.
34
+ * - Opening IndexedDB is defected during layer acquisition if the database
35
+ * cannot be opened; later store operations report `PersistenceError`.
36
+ * - Values must be structured-cloneable objects, and writes can fail because of
37
+ * quota limits, browser settings, private browsing modes, or user-cleared
38
+ * storage.
39
+ * - Lazy TTL cleanup makes this suitable for caches, not for authoritative or
40
+ * security-sensitive state.
41
+ *
42
+ * **Example** (Providing browser persistence)
43
+ *
44
+ * ```ts
45
+ * import { BrowserPersistence } from "@effect/platform-browser"
46
+ *
47
+ * export const PersistenceLive = BrowserPersistence.layerIndexedDb({
48
+ * database: "my-app-cache"
49
+ * })
50
+ * ```
51
+ *
52
+ * @since 4.0.0
53
+ */
54
+ import type * as Arr from "effect/Array"
55
+ import * as Clock from "effect/Clock"
56
+ import type * as Duration from "effect/Duration"
57
+ import * as Effect from "effect/Effect"
58
+ import * as Layer from "effect/Layer"
59
+ import * as Persistence from "effect/unstable/persistence/Persistence"
60
+
61
+ /**
62
+ * Creates a `BackingPersistence` layer backed by IndexedDB, optionally using the provided database name.
63
+ *
64
+ * **When to use**
65
+ *
66
+ * Use when composing persistence manually and the lower-level
67
+ * `BackingPersistence` service should be backed by browser IndexedDB.
68
+ *
69
+ * **Details**
70
+ *
71
+ * The database name defaults to `"effect_persistence"`. Entries are stored by
72
+ * persistence store id and key in a shared object store, and TTL expiration is
73
+ * checked when values are read.
74
+ *
75
+ * **Gotchas**
76
+ *
77
+ * Opening the database is defected during layer acquisition if IndexedDB is
78
+ * unavailable or cannot be opened. Store operations report `PersistenceError`
79
+ * for IndexedDB request, transaction, quota, and structured-clone failures.
80
+ *
81
+ * @see {@link layerIndexedDb} for providing the higher-level `Persistence` service
82
+ *
83
+ * @category layers
84
+ * @since 4.0.0
85
+ */
86
+ export const layerBackingIndexedDb = (options?: {
87
+ readonly database?: string | undefined
88
+ }): Layer.Layer<Persistence.BackingPersistence> =>
89
+ Layer.effect(Persistence.BackingPersistence)(Effect.gen(function*() {
90
+ const db = yield* Effect.acquireRelease(
91
+ openDatabase(options?.database ?? defaultDatabase),
92
+ (db) => Effect.sync(() => db.close())
93
+ ).pipe(Effect.orDie)
94
+
95
+ return Persistence.BackingPersistence.of({
96
+ make: Effect.fnUntraced(function*(storeId) {
97
+ const clock = yield* Clock.Clock
98
+ return {
99
+ get: (key) => get(db, clock, storeId, key),
100
+ getMany: (keys) => getMany(db, clock, storeId, keys),
101
+ set: (key, value, ttl) => set(db, clock, storeId, key, value, ttl),
102
+ setMany: (entries) => setMany(db, clock, storeId, entries),
103
+ remove: (key) => remove(db, storeId, key),
104
+ clear: clear(db, storeId)
105
+ }
106
+ })
107
+ })
108
+ }))
109
+
110
+ const defaultDatabase = "effect_persistence"
111
+ const databaseVersion = 1
112
+ const entriesStoreName = "entries"
113
+ const storeIdIndexName = "storeId"
114
+
115
+ /**
116
+ * Creates a `Persistence` layer backed by IndexedDB, optionally using the provided database name.
117
+ *
118
+ * @category layers
119
+ * @since 4.0.0
120
+ */
121
+ export const layerIndexedDb = (options?: {
122
+ readonly database?: string | undefined
123
+ }): Layer.Layer<Persistence.Persistence> =>
124
+ Persistence.layer.pipe(
125
+ Layer.provide(layerBackingIndexedDb(options))
126
+ )
127
+
128
+ const openDatabase = (database: string): Effect.Effect<IDBDatabase, Persistence.PersistenceError> =>
129
+ Effect.gen(function*() {
130
+ const openRequest = yield* Effect.try({
131
+ try: () => globalThis.indexedDB.open(database, databaseVersion),
132
+ catch: (cause) =>
133
+ new Persistence.PersistenceError({
134
+ message: "Failed to open backing store database",
135
+ cause
136
+ })
137
+ })
138
+
139
+ openRequest.onupgradeneeded = () => {
140
+ const db = openRequest.result
141
+ const entries = db.objectStoreNames.contains(entriesStoreName)
142
+ ? openRequest.transaction?.objectStore(entriesStoreName)
143
+ : db.createObjectStore(entriesStoreName, { keyPath: ["storeId", "id"] })
144
+ if (entries && !entries.indexNames.contains(storeIdIndexName)) {
145
+ entries.createIndex(storeIdIndexName, storeIdIndexName, { unique: false })
146
+ }
147
+ }
148
+
149
+ return yield* idbRequest("Failed to open backing store database", () => openRequest)
150
+ })
151
+
152
+ interface EntryRow {
153
+ readonly storeId: string
154
+ readonly id: string
155
+ readonly value: object
156
+ readonly expires: number | null
157
+ }
158
+
159
+ const isExpired = (row: EntryRow, now: number): boolean => row.expires !== null && row.expires <= now
160
+
161
+ const get = (
162
+ db: IDBDatabase,
163
+ clock: Clock.Clock,
164
+ storeId: string,
165
+ key: string
166
+ ): Effect.Effect<object | undefined, Persistence.PersistenceError> =>
167
+ withEntriesTransaction<object | undefined>(
168
+ db,
169
+ "readwrite",
170
+ `Failed to get key ${key} from backing store`,
171
+ (
172
+ entries,
173
+ setResult,
174
+ fail
175
+ ) => {
176
+ const now = clock.currentTimeMillisUnsafe()
177
+ const id: [string, string] = [storeId, key]
178
+ const request = entries.get(id)
179
+ request.onerror = () => fail(request.error)
180
+ request.onsuccess = () => {
181
+ const row = request.result as EntryRow | undefined
182
+ if (!row || !isExpired(row, now)) {
183
+ setResult(row?.value)
184
+ return
185
+ }
186
+
187
+ const deleteRequest = entries.delete(id)
188
+ deleteRequest.onerror = () => fail(deleteRequest.error)
189
+ deleteRequest.onsuccess = () => setResult(undefined)
190
+ }
191
+ }
192
+ )
193
+
194
+ const getMany = (
195
+ db: IDBDatabase,
196
+ clock: Clock.Clock,
197
+ storeId: string,
198
+ keys: Arr.NonEmptyArray<string>
199
+ ): Effect.Effect<Arr.NonEmptyArray<object | undefined>, Persistence.PersistenceError> =>
200
+ withEntriesTransaction(
201
+ db,
202
+ "readwrite",
203
+ "Failed to getMany from backing store",
204
+ (entries, setResult, fail) => {
205
+ const now = clock.currentTimeMillisUnsafe()
206
+ const results = new Array<object | undefined>(keys.length)
207
+ setResult(results as any)
208
+
209
+ for (let i = 0; i < keys.length; i++) {
210
+ const key = keys[i]
211
+ const keyPath = [storeId, key]
212
+ const request = entries.get(keyPath)
213
+ request.onerror = () => fail(request.error)
214
+ request.onsuccess = () => {
215
+ const row = request.result as EntryRow | undefined
216
+ if (!row) return
217
+ else if (!isExpired(row, now)) {
218
+ results[i] = row.value
219
+ return
220
+ }
221
+ const deleteRequest = entries.delete(keyPath)
222
+ deleteRequest.onerror = () => fail(deleteRequest.error)
223
+ }
224
+ }
225
+ }
226
+ )
227
+
228
+ const set = (
229
+ db: IDBDatabase,
230
+ clock: Clock.Clock,
231
+ storeId: string,
232
+ key: string,
233
+ value: object,
234
+ ttl: Duration.Duration | undefined
235
+ ): Effect.Effect<void, Persistence.PersistenceError> =>
236
+ withEntriesTransaction(
237
+ db,
238
+ "readwrite",
239
+ `Failed to set key ${key} in backing store`,
240
+ (entries, setResult, fail) => {
241
+ const request = entries.put(
242
+ {
243
+ storeId,
244
+ id: key,
245
+ value,
246
+ expires: Persistence.unsafeTtlToExpires(clock, ttl)
247
+ } satisfies EntryRow
248
+ )
249
+ request.onerror = () => fail(request.error)
250
+ request.onsuccess = () => setResult(undefined)
251
+ }
252
+ )
253
+
254
+ const setMany = (
255
+ db: IDBDatabase,
256
+ clock: Clock.Clock,
257
+ storeId: string,
258
+ entries: Arr.NonEmptyArray<readonly [key: string, value: object, ttl: Duration.Duration | undefined]>
259
+ ): Effect.Effect<void, Persistence.PersistenceError> =>
260
+ withEntriesTransaction(
261
+ db,
262
+ "readwrite",
263
+ "Failed to setMany in backing store",
264
+ (store, setResult, fail) => {
265
+ for (const [key, value, ttl] of entries) {
266
+ const request = store.put(
267
+ {
268
+ storeId,
269
+ id: key,
270
+ value,
271
+ expires: Persistence.unsafeTtlToExpires(clock, ttl)
272
+ } satisfies EntryRow
273
+ )
274
+ request.onerror = () => fail(request.error)
275
+ request.onsuccess = () => setResult(undefined)
276
+ }
277
+ }
278
+ )
279
+
280
+ const remove = (
281
+ db: IDBDatabase,
282
+ storeId: string,
283
+ key: string
284
+ ): Effect.Effect<void, Persistence.PersistenceError> =>
285
+ withEntriesTransaction(
286
+ db,
287
+ "readwrite",
288
+ `Failed to remove key ${key} from backing store`,
289
+ (entries, setResult, fail) => {
290
+ const request = entries.delete([storeId, key])
291
+ request.onerror = () => fail(request.error)
292
+ request.onsuccess = () => setResult(undefined)
293
+ }
294
+ )
295
+
296
+ const clear = (db: IDBDatabase, storeId: string): Effect.Effect<void, Persistence.PersistenceError> =>
297
+ withEntriesTransaction(db, "readwrite", "Failed to clear backing store", (entries, setResult, fail) => {
298
+ const index = entries.index(storeIdIndexName)
299
+ const cursorRequest = index.openCursor(storeId)
300
+ cursorRequest.onerror = () => fail(cursorRequest.error)
301
+ cursorRequest.onsuccess = () => {
302
+ const cursor = cursorRequest.result
303
+ if (!cursor) {
304
+ setResult(undefined)
305
+ return
306
+ }
307
+ const deleteRequest = cursor.delete()
308
+ deleteRequest.onerror = () => fail(deleteRequest.error)
309
+ deleteRequest.onsuccess = () => cursor.continue()
310
+ }
311
+ })
312
+
313
+ const withEntriesTransaction = <A>(
314
+ db: IDBDatabase,
315
+ mode: IDBTransactionMode,
316
+ message: string,
317
+ run: (
318
+ entries: IDBObjectStore,
319
+ onResult: (result: A) => void,
320
+ fail: (cause: unknown) => void
321
+ ) => void
322
+ ): Effect.Effect<A, Persistence.PersistenceError> =>
323
+ Effect.callback<A, Persistence.PersistenceError>((resume) => {
324
+ const tx = db.transaction(entriesStoreName, mode)
325
+ const entries = tx.objectStore(entriesStoreName)
326
+
327
+ let result: A | undefined
328
+ let setResult = false
329
+ let done = false
330
+
331
+ const fail = (cause: unknown) => {
332
+ done = true
333
+ resume(Effect.fail(new Persistence.PersistenceError({ message, cause })))
334
+ }
335
+
336
+ tx.oncomplete = () => {
337
+ done = true
338
+ if (setResult) resume(Effect.succeed(result!))
339
+ }
340
+ tx.onerror = () => {
341
+ done = true
342
+ fail(tx.error)
343
+ }
344
+ tx.onabort = () => {
345
+ done = true
346
+ fail(tx.error)
347
+ }
348
+
349
+ run(entries, (next) => {
350
+ if (done) return resume(Effect.succeed(next))
351
+ setResult = true
352
+ result = next
353
+ }, fail)
354
+
355
+ return Effect.sync(() => {
356
+ tx.abort()
357
+ })
358
+ })
359
+
360
+ const idbRequest = <A>(
361
+ message: string,
362
+ evaluate: () => IDBRequest<A>
363
+ ): Effect.Effect<A, Persistence.PersistenceError> =>
364
+ Effect.callback<A, Persistence.PersistenceError>((resume) => {
365
+ const request = evaluate()
366
+ const fail = (cause: unknown) => {
367
+ resume(Effect.fail(new Persistence.PersistenceError({ message, cause })))
368
+ }
369
+ if (request.readyState === "done") {
370
+ resume(Effect.succeed(request.result))
371
+ }
372
+ request.onsuccess = () => {
373
+ resume(Effect.succeed(request.result))
374
+ }
375
+ request.onerror = () => fail(request.error)
376
+ })
@@ -1,17 +1,74 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Browser entry-point helpers for running Effect programs.
3
+ *
4
+ * This module exposes `runMain`, a browser-oriented main runner for launching
5
+ * an Effect as the root program of a page, single-page application, demo, or
6
+ * browser test harness. It delegates execution to the core Effect runtime while
7
+ * adding the browser lifecycle hook needed to interrupt the main fiber when the
8
+ * page receives `beforeunload`.
9
+ *
10
+ * `BrowserRuntime` does not provide application services by itself. Provide
11
+ * any required layers, such as browser HTTP, storage, worker, geolocation, or
12
+ * permission services, before passing the effect to `runMain`. Keep long-lived
13
+ * browser resources scoped so interruption can run their finalizers while the
14
+ * page is still active.
15
+ *
16
+ * Browser unload is more constrained than a process signal. Finalizers that
17
+ * need the network, timers, prompts, or long asynchronous work may not complete
18
+ * once navigation or tab close has started, and browsers do not expose a
19
+ * process exit status. Use `runMain` to connect the page lifecycle to Effect
20
+ * interruption, and use browser-specific persistence or delivery APIs for work
21
+ * that must survive page teardown.
22
+ *
23
+ * @since 4.0.0
3
24
  */
4
25
  import type * as Effect from "effect/Effect"
5
26
  import { makeRunMain, type Teardown } from "effect/Runtime"
6
27
 
7
28
  /**
8
- * @since 1.0.0
29
+ * Runs an effect as the browser main program and interrupts its fiber when the page receives a `beforeunload` event.
30
+ *
31
+ * **When to use**
32
+ *
33
+ * Use to launch a browser page, single-page application, demo, or browser test
34
+ * harness as a root Effect program.
35
+ *
36
+ * **Details**
37
+ *
38
+ * Supports both direct and curried call forms. Options are forwarded to
39
+ * `makeRunMain`, including `disableErrorReporting` and custom `teardown`
40
+ * behavior.
41
+ *
42
+ * **Gotchas**
43
+ *
44
+ * The `beforeunload` interruption is best-effort. Browser teardown may prevent
45
+ * asynchronous finalizers, network work, timers, or prompts from completing.
46
+ *
9
47
  * @category Runtime
48
+ * @since 4.0.0
10
49
  */
11
50
  export const runMain: {
12
51
  /**
13
- * @since 1.0.0
52
+ * Runs an effect as the browser main program and interrupts its fiber when the page receives a `beforeunload` event.
53
+ *
54
+ * **When to use**
55
+ *
56
+ * Use to launch a browser page, single-page application, demo, or browser test
57
+ * harness as a root Effect program.
58
+ *
59
+ * **Details**
60
+ *
61
+ * Supports both direct and curried call forms. Options are forwarded to
62
+ * `makeRunMain`, including `disableErrorReporting` and custom `teardown`
63
+ * behavior.
64
+ *
65
+ * **Gotchas**
66
+ *
67
+ * The `beforeunload` interruption is best-effort. Browser teardown may prevent
68
+ * asynchronous finalizers, network work, timers, or prompts from completing.
69
+ *
14
70
  * @category Runtime
71
+ * @since 4.0.0
15
72
  */
16
73
  (
17
74
  options?: {
@@ -20,8 +77,26 @@ export const runMain: {
20
77
  }
21
78
  ): <E, A>(effect: Effect.Effect<A, E>) => void
22
79
  /**
23
- * @since 1.0.0
80
+ * Runs an effect as the browser main program and interrupts its fiber when the page receives a `beforeunload` event.
81
+ *
82
+ * **When to use**
83
+ *
84
+ * Use to launch a browser page, single-page application, demo, or browser test
85
+ * harness as a root Effect program.
86
+ *
87
+ * **Details**
88
+ *
89
+ * Supports both direct and curried call forms. Options are forwarded to
90
+ * `makeRunMain`, including `disableErrorReporting` and custom `teardown`
91
+ * behavior.
92
+ *
93
+ * **Gotchas**
94
+ *
95
+ * The `beforeunload` interruption is best-effort. Browser teardown may prevent
96
+ * asynchronous finalizers, network work, timers, or prompts from completing.
97
+ *
24
98
  * @category Runtime
99
+ * @since 4.0.0
25
100
  */
26
101
  <E, A>(
27
102
  effect: Effect.Effect<A, E>,
@@ -1,12 +1,70 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Provide Effect sockets backed by the browser `WebSocket` implementation.
3
+ *
4
+ * This module is the browser entry point for Effect's socket abstraction. Use
5
+ * {@link layerWebSocket} when a client-side Effect program needs a complete
6
+ * `Socket.Socket` connected to a WebSocket URL. Use
7
+ * {@link layerWebSocketConstructor} when lower-level socket code only needs the
8
+ * browser-backed constructor service.
9
+ *
10
+ * ## Mental model
11
+ *
12
+ * `layerWebSocket` delegates socket behavior to Effect's WebSocket support and
13
+ * supplies `globalThis.WebSocket` as the constructor. Incoming browser messages
14
+ * are normalized to strings or binary `Uint8Array` values. Browser `Blob`
15
+ * messages are read into bytes before they reach the socket consumer.
16
+ *
17
+ * Outgoing data should already be serialized to a string or bytes. To close the
18
+ * underlying browser socket with a specific code and reason, send a
19
+ * `CloseEvent` value so the close metadata is preserved.
20
+ *
21
+ * ## Common tasks
22
+ *
23
+ * - Connect RPC transports, browser tests, or realtime UI features to a
24
+ * WebSocket URL with {@link layerWebSocket}.
25
+ * - Provide only the browser constructor service with
26
+ * {@link layerWebSocketConstructor} when another socket layer builds the
27
+ * connection.
28
+ * - Customize `closeCodeIsError` for protocols that treat specific close codes
29
+ * as normal completion instead of socket failure.
30
+ *
31
+ * ## Gotchas
32
+ *
33
+ * Browser WebSocket rules still apply. URL schemes, subprotocol negotiation,
34
+ * mixed-content blocking, cookies, authentication, server origin checks, and
35
+ * extension negotiation are controlled by the browser and server rather than by
36
+ * Effect. Close events become socket errors unless `closeCodeIsError`
37
+ * classifies the close code as clean.
38
+ *
39
+ * @since 4.0.0
3
40
  */
4
41
  import * as Layer from "effect/Layer"
5
42
  import * as Socket from "effect/unstable/socket/Socket"
6
43
 
7
44
  /**
8
- * @since 1.0.0
9
- * @category Layers
45
+ * Creates a `Socket` layer connected to the given URL using the browser `WebSocket` constructor.
46
+ *
47
+ * **When to use**
48
+ *
49
+ * Use when browser or client-side code needs a complete `Socket` layer
50
+ * connected to a WebSocket URL.
51
+ *
52
+ * **Details**
53
+ *
54
+ * Delegates socket construction to `Socket.makeWebSocket` and provides the
55
+ * browser-backed `WebSocketConstructor` service.
56
+ *
57
+ * **Gotchas**
58
+ *
59
+ * Browser WebSocket rules still control URL schemes, mixed-content blocking,
60
+ * cookies, authentication, origin checks, subprotocols, and extensions. Close
61
+ * events are errors unless `closeCodeIsError` classifies the close code as
62
+ * clean.
63
+ *
64
+ * @see {@link layerWebSocketConstructor} for providing only the browser constructor service
65
+ *
66
+ * @category layers
67
+ * @since 4.0.0
10
68
  */
11
69
  export const layerWebSocket = (url: string, options?: {
12
70
  readonly closeCodeIsError?: (code: number) => boolean
@@ -16,10 +74,10 @@ export const layerWebSocket = (url: string, options?: {
16
74
  )
17
75
 
18
76
  /**
19
- * A WebSocket constructor that uses `globalThis.WebSocket`.
77
+ * Layer that provides a `WebSocketConstructor` service backed by `globalThis.WebSocket`.
20
78
  *
21
- * @since 1.0.0
22
- * @category Layers
79
+ * @category layers
80
+ * @since 4.0.0
23
81
  */
24
82
  export const layerWebSocketConstructor: Layer.Layer<Socket.WebSocketConstructor> =
25
83
  Socket.layerWebSocketConstructorGlobal
@@ -1,5 +1,42 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Convert browser DOM events into Effect streams.
3
+ *
4
+ * This module provides typed constructors for listening to `window` and
5
+ * `document` events from Effect programs. Use {@link fromEventListenerWindow}
6
+ * for viewport, network, focus, pointer, keyboard, and other `Window` events,
7
+ * and use {@link fromEventListenerDocument} for document lifecycle,
8
+ * visibility, selection, fullscreen, and other `Document` events.
9
+ *
10
+ * ## Mental model
11
+ *
12
+ * Each constructor registers a DOM `addEventListener` callback when the stream
13
+ * is consumed and removes it when the stream is finalized. Browser events are
14
+ * push-based `EventTarget` notifications, so the browser does not slow down the
15
+ * event source when downstream stream processing is busy. Events are buffered
16
+ * inside the stream until a consumer pulls them.
17
+ *
18
+ * ## Common tasks
19
+ *
20
+ * - Track browser state such as resize, online / offline, focus, visibility, or
21
+ * pointer activity with Effect stream operators.
22
+ * - Scope DOM listeners to a fiber so they are removed when the consuming
23
+ * effect is interrupted or completes.
24
+ * - Set `bufferSize` for bursty event sources before applying sampling,
25
+ * throttling, debouncing, or dropping logic downstream.
26
+ *
27
+ * ## Gotchas
28
+ *
29
+ * The default buffer is unbounded. High-frequency sources such as `scroll`,
30
+ * `pointermove`, or `mousemove` should usually specify `bufferSize` and reduce
31
+ * the event rate with stream operators.
32
+ *
33
+ * These helpers are for DOM events, not for `ReadableStream` request or
34
+ * response bodies. Fetch bodies follow Web Streams rules such as
35
+ * single-consumer locking and disturbed bodies after reads. When using the DOM
36
+ * `once` option, also use `Stream.take(1)` if the consuming code needs a finite
37
+ * stream.
38
+ *
39
+ * @since 4.0.0
3
40
  */
4
41
 
5
42
  import * as Stream from "effect/Stream"
@@ -7,11 +44,14 @@ import * as Stream from "effect/Stream"
7
44
  /**
8
45
  * Creates a `Stream` from `window.addEventListener`.
9
46
  *
47
+ * **Details**
48
+ *
10
49
  * By default, the underlying buffer is unbounded in size. You can customize the
11
- * buffer size an object as the second argument with the `bufferSize` field.
50
+ * buffer size by passing an object as the second argument with the `bufferSize`
51
+ * field.
12
52
  *
13
- * @since 1.0.0
14
53
  * @category Streams
54
+ * @since 4.0.0
15
55
  */
16
56
  export const fromEventListenerWindow = <K extends keyof WindowEventMap>(
17
57
  type: K,
@@ -26,11 +66,14 @@ export const fromEventListenerWindow = <K extends keyof WindowEventMap>(
26
66
  /**
27
67
  * Creates a `Stream` from `document.addEventListener`.
28
68
  *
69
+ * **Details**
70
+ *
29
71
  * By default, the underlying buffer is unbounded in size. You can customize the
30
- * buffer size an object as the second argument with the `bufferSize` field.
72
+ * buffer size by passing an object as the second argument with the `bufferSize`
73
+ * field.
31
74
  *
32
- * @since 1.0.0
33
75
  * @category Streams
76
+ * @since 4.0.0
34
77
  */
35
78
  export const fromEventListenerDocument = <K extends keyof DocumentEventMap>(
36
79
  type: K,