@effect-atom/atom 0.1.0

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 (65) hide show
  1. package/Atom/package.json +6 -0
  2. package/AtomRef/package.json +6 -0
  3. package/Hydration/package.json +6 -0
  4. package/LICENSE +21 -0
  5. package/README.md +3 -0
  6. package/Registry/package.json +6 -0
  7. package/Result/package.json +6 -0
  8. package/dist/cjs/Atom.js +1079 -0
  9. package/dist/cjs/Atom.js.map +1 -0
  10. package/dist/cjs/AtomRef.js +261 -0
  11. package/dist/cjs/AtomRef.js.map +1 -0
  12. package/dist/cjs/Hydration.js +100 -0
  13. package/dist/cjs/Hydration.js.map +1 -0
  14. package/dist/cjs/Registry.js +128 -0
  15. package/dist/cjs/Registry.js.map +1 -0
  16. package/dist/cjs/Result.js +454 -0
  17. package/dist/cjs/Result.js.map +1 -0
  18. package/dist/cjs/index.js +37 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/internal/registry.js +701 -0
  21. package/dist/cjs/internal/registry.js.map +1 -0
  22. package/dist/cjs/internal/runtime.js +92 -0
  23. package/dist/cjs/internal/runtime.js.map +1 -0
  24. package/dist/dts/Atom.d.ts +597 -0
  25. package/dist/dts/Atom.d.ts.map +1 -0
  26. package/dist/dts/AtomRef.d.ts +55 -0
  27. package/dist/dts/AtomRef.d.ts.map +1 -0
  28. package/dist/dts/Hydration.d.ts +27 -0
  29. package/dist/dts/Hydration.d.ts.map +1 -0
  30. package/dist/dts/Registry.d.ts +115 -0
  31. package/dist/dts/Registry.d.ts.map +1 -0
  32. package/dist/dts/Result.d.ts +351 -0
  33. package/dist/dts/Result.d.ts.map +1 -0
  34. package/dist/dts/index.d.ts +21 -0
  35. package/dist/dts/index.d.ts.map +1 -0
  36. package/dist/dts/internal/registry.d.ts +2 -0
  37. package/dist/dts/internal/registry.d.ts.map +1 -0
  38. package/dist/dts/internal/runtime.d.ts +2 -0
  39. package/dist/dts/internal/runtime.d.ts.map +1 -0
  40. package/dist/esm/Atom.js +1029 -0
  41. package/dist/esm/Atom.js.map +1 -0
  42. package/dist/esm/AtomRef.js +232 -0
  43. package/dist/esm/AtomRef.js.map +1 -0
  44. package/dist/esm/Hydration.js +71 -0
  45. package/dist/esm/Hydration.js.map +1 -0
  46. package/dist/esm/Registry.js +98 -0
  47. package/dist/esm/Registry.js.map +1 -0
  48. package/dist/esm/Result.js +403 -0
  49. package/dist/esm/Result.js.map +1 -0
  50. package/dist/esm/index.js +21 -0
  51. package/dist/esm/index.js.map +1 -0
  52. package/dist/esm/internal/registry.js +672 -0
  53. package/dist/esm/internal/registry.js.map +1 -0
  54. package/dist/esm/internal/runtime.js +64 -0
  55. package/dist/esm/internal/runtime.js.map +1 -0
  56. package/dist/esm/package.json +4 -0
  57. package/package.json +72 -0
  58. package/src/Atom.ts +1865 -0
  59. package/src/AtomRef.ts +282 -0
  60. package/src/Hydration.ts +98 -0
  61. package/src/Registry.ts +204 -0
  62. package/src/Result.ts +767 -0
  63. package/src/index.ts +24 -0
  64. package/src/internal/registry.ts +810 -0
  65. package/src/internal/runtime.ts +63 -0
package/src/Atom.ts ADDED
@@ -0,0 +1,1865 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ /* eslint-disable @typescript-eslint/no-empty-object-type */
5
+ import * as KeyValueStore from "@effect/platform/KeyValueStore"
6
+ import * as Arr from "effect/Array"
7
+ import { NoSuchElementException } from "effect/Cause"
8
+ import * as Cause from "effect/Cause"
9
+ import * as Channel from "effect/Channel"
10
+ import * as Chunk from "effect/Chunk"
11
+ import * as EffectContext from "effect/Context"
12
+ import * as Duration from "effect/Duration"
13
+ import * as Effect from "effect/Effect"
14
+ import * as Either from "effect/Either"
15
+ import * as Exit from "effect/Exit"
16
+ import * as Fiber from "effect/Fiber"
17
+ import * as FiberRef from "effect/FiberRef"
18
+ import type { LazyArg } from "effect/Function"
19
+ import { constant, constVoid, dual, pipe } from "effect/Function"
20
+ import { globalValue } from "effect/GlobalValue"
21
+ import * as Inspectable from "effect/Inspectable"
22
+ import * as Layer from "effect/Layer"
23
+ import * as MutableHashMap from "effect/MutableHashMap"
24
+ import * as Option from "effect/Option"
25
+ import { type Pipeable, pipeArguments } from "effect/Pipeable"
26
+ import * as Runtime from "effect/Runtime"
27
+ import * as Schema from "effect/Schema"
28
+ import * as Scope from "effect/Scope"
29
+ import * as Stream from "effect/Stream"
30
+ import * as Subscribable from "effect/Subscribable"
31
+ import * as SubscriptionRef from "effect/SubscriptionRef"
32
+ import type { NoInfer } from "effect/Types"
33
+ import * as internalRegistry from "./internal/registry.js"
34
+ import { runCallbackSync } from "./internal/runtime.js"
35
+ import * as Registry from "./Registry.js"
36
+ import { AtomRegistry as AtomRegistry } from "./Registry.js"
37
+ import * as Result from "./Result.js"
38
+
39
+ /**
40
+ * @since 1.0.0
41
+ * @category type ids
42
+ */
43
+ export const TypeId: TypeId = "~effect-atom/atom/Atom"
44
+
45
+ /**
46
+ * @since 1.0.0
47
+ * @category type ids
48
+ */
49
+ export type TypeId = "~effect-atom/atom/Atom"
50
+
51
+ /**
52
+ * @since 1.0.0
53
+ * @category models
54
+ */
55
+ export interface Atom<A> extends Pipeable, Inspectable.Inspectable {
56
+ readonly [TypeId]: TypeId
57
+ readonly keepAlive: boolean
58
+ readonly lazy: boolean
59
+ readonly read: (get: Context) => A
60
+ readonly refresh?: (f: <A>(atom: Atom<A>) => void) => void
61
+ readonly label?: readonly [name: string, stack: string]
62
+ readonly idleTTL?: number
63
+ }
64
+
65
+ /**
66
+ * @since 1.0.0
67
+ */
68
+ export type Type<T extends Atom<any>> = T extends Atom<infer A> ? A : never
69
+
70
+ /**
71
+ * @since 1.0.0
72
+ */
73
+ export type Success<T extends Atom<any>> = T extends Atom<Result.Result<infer A, infer _>> ? A : never
74
+
75
+ /**
76
+ * @since 1.0.0
77
+ */
78
+ export type PullSuccess<T extends Atom<any>> = T extends Atom<PullResult<infer A, infer _>> ? A : never
79
+
80
+ /**
81
+ * @since 1.0.0
82
+ */
83
+ export type Failure<T extends Atom<any>> = T extends Atom<Result.Result<infer _, infer E>> ? E : never
84
+
85
+ /**
86
+ * @since 1.0.0
87
+ */
88
+ export type WithoutSerializable<T extends Atom<any>> = T extends Writable<infer R, infer W> ? Writable<R, W>
89
+ : Atom<Type<T>>
90
+
91
+ /**
92
+ * @since 1.0.0
93
+ * @category type ids
94
+ */
95
+ export const WritableTypeId: WritableTypeId = "~effect-atom/atom/Atom/Writable"
96
+
97
+ /**
98
+ * @since 1.0.0
99
+ * @category type ids
100
+ */
101
+ export type WritableTypeId = "~effect-atom/atom/Atom/Writable"
102
+
103
+ /**
104
+ * @since 1.0.0
105
+ * @category models
106
+ */
107
+ export interface Writable<R, W = R> extends Atom<R> {
108
+ readonly [WritableTypeId]: WritableTypeId
109
+ readonly write: (ctx: WriteContext<R>, value: W) => void
110
+ }
111
+
112
+ /**
113
+ * @since 1.0.0
114
+ * @category context
115
+ */
116
+ export interface Context {
117
+ <A>(atom: Atom<A>): A
118
+ readonly get: <A>(atom: Atom<A>) => A
119
+ readonly result: <A, E>(atom: Atom<Result.Result<A, E>>, options?: {
120
+ readonly suspendOnWaiting?: boolean | undefined
121
+ }) => Effect.Effect<A, E>
122
+ readonly resultOnce: <A, E>(atom: Atom<Result.Result<A, E>>, options?: {
123
+ readonly suspendOnWaiting?: boolean | undefined
124
+ }) => Effect.Effect<A, E>
125
+ readonly once: <A>(atom: Atom<A>) => A
126
+ readonly addFinalizer: (f: () => void) => void
127
+ readonly mount: <A>(atom: Atom<A>) => void
128
+ readonly refresh: <A>(atom: Atom<A>) => void
129
+ readonly refreshSelf: () => void
130
+ readonly self: <A>() => Option.Option<A>
131
+ readonly setSelf: <A>(a: A) => void
132
+ readonly set: <R, W>(atom: Writable<R, W>, value: W) => void
133
+ readonly some: <A>(atom: Atom<Option.Option<A>>) => Effect.Effect<A>
134
+ readonly someOnce: <A>(atom: Atom<Option.Option<A>>) => Effect.Effect<A>
135
+ readonly stream: <A>(atom: Atom<A>, options?: {
136
+ readonly withoutInitialValue?: boolean
137
+ readonly bufferSize?: number
138
+ }) => Stream.Stream<A>
139
+ readonly streamResult: <A, E>(atom: Atom<Result.Result<A, E>>, options?: {
140
+ readonly withoutInitialValue?: boolean
141
+ readonly bufferSize?: number
142
+ }) => Stream.Stream<A, E>
143
+ readonly subscribe: <A>(atom: Atom<A>, f: (_: A) => void, options?: {
144
+ readonly immediate?: boolean
145
+ }) => void
146
+ readonly registry: Registry.Registry
147
+ }
148
+
149
+ /**
150
+ * @since 1.0.0
151
+ * @category context
152
+ */
153
+ export interface WriteContext<A> {
154
+ readonly get: <A>(atom: Atom<A>) => A
155
+ readonly refreshSelf: () => void
156
+ readonly setSelf: (a: A) => void
157
+ readonly set: <R, W>(atom: Writable<R, W>, value: W) => void
158
+ }
159
+
160
+ const AtomProto = {
161
+ [TypeId]: TypeId,
162
+ pipe() {
163
+ return pipeArguments(this, arguments)
164
+ },
165
+ toJSON(this: Atom<any>) {
166
+ return {
167
+ _id: "Atom",
168
+ keepAlive: this.keepAlive,
169
+ lazy: this.lazy,
170
+ label: this.label
171
+ }
172
+ },
173
+ toString() {
174
+ return Inspectable.format(this)
175
+ },
176
+ [Inspectable.NodeInspectSymbol](this: Atom<any>) {
177
+ return this.toJSON()
178
+ }
179
+ } as const
180
+
181
+ const RuntimeProto = {
182
+ ...AtomProto,
183
+ atom(this: AtomRuntime<any, any>, arg: any, options?: { readonly initialValue?: unknown }) {
184
+ const read = makeRead(arg, options)
185
+ return readable((get) => {
186
+ const previous = get.self<Result.Result<any, any>>()
187
+ const runtimeResult = get(this)
188
+ if (runtimeResult._tag !== "Success") {
189
+ return Result.replacePrevious(runtimeResult, previous)
190
+ }
191
+ return read(get, runtimeResult.value)
192
+ })
193
+ },
194
+
195
+ fn(this: AtomRuntime<any, any>, arg: any, options?: { readonly initialValue?: unknown }) {
196
+ if (arguments.length === 0) {
197
+ return (arg: any, options?: { readonly initialValue?: unknown }) => makeFnRuntime(this, arg, options)
198
+ }
199
+ return makeFnRuntime(this, arg, options)
200
+ },
201
+
202
+ pull(this: AtomRuntime<any, any>, arg: any, options?: {
203
+ readonly disableAccumulation?: boolean
204
+ readonly initialValue?: ReadonlyArray<any>
205
+ }) {
206
+ const pullSignal = state(0)
207
+ const pullAtom = readable((get) => {
208
+ const previous = get.self<Result.Result<any, any>>()
209
+ const runtimeResult = get(this)
210
+ if (runtimeResult._tag !== "Success") {
211
+ return Result.replacePrevious(runtimeResult, previous)
212
+ }
213
+ return makeEffect(
214
+ get,
215
+ makeStreamPullEffect(get, pullSignal, arg, options),
216
+ Result.initial(true),
217
+ runtimeResult.value
218
+ )
219
+ })
220
+ return makeStreamPull(pullSignal, pullAtom)
221
+ },
222
+
223
+ subscriptionRef(this: AtomRuntime<any, any>, ref: any) {
224
+ return makeSubRef(
225
+ readable((get) => {
226
+ const previous = get.self<Result.Result<any, any>>()
227
+ const runtimeResult = get(this)
228
+ if (runtimeResult._tag !== "Success") {
229
+ return Result.replacePrevious(runtimeResult, previous)
230
+ }
231
+ const value = typeof ref === "function" ? ref(get) : ref
232
+ return SubscriptionRef.SubscriptionRefTypeId in value
233
+ ? value
234
+ : makeEffect(get, value, Result.initial(true), runtimeResult.value)
235
+ }),
236
+ (get, ref) => {
237
+ const runtime = Result.getOrThrow(get(this))
238
+ return readSubscribable(get, ref, runtime)
239
+ }
240
+ )
241
+ },
242
+
243
+ subscribable(this: AtomRuntime<any, any>, arg: any) {
244
+ return makeSubscribable(
245
+ readable((get) => {
246
+ const previous = get.self<Result.Result<any, any>>()
247
+ const runtimeResult = get(this)
248
+ if (runtimeResult._tag !== "Success") {
249
+ return Result.replacePrevious(runtimeResult, previous)
250
+ }
251
+ const value = typeof arg === "function" ? arg(get) : arg
252
+ return Subscribable.isSubscribable(value) ?
253
+ value as Subscribable.Subscribable<any, any>
254
+ : makeEffect(get, value, Result.initial(true), runtimeResult.value)
255
+ }),
256
+ (get, ref) => {
257
+ const runtime = Result.getOrThrow(get(this))
258
+ return readSubscribable(get, ref, runtime)
259
+ }
260
+ )
261
+ }
262
+ }
263
+
264
+ const makeFnRuntime = (self: AtomRuntime<any, any>, arg: any, options?: { readonly initialValue?: unknown }) => {
265
+ const [read, write, argAtom] = makeResultFn(arg, options)
266
+ return writable((get) => {
267
+ get.get(argAtom)
268
+ const previous = get.self<Result.Result<any, any>>()
269
+ const runtimeResult = get.get(self)
270
+ if (runtimeResult._tag !== "Success") {
271
+ return Result.replacePrevious(runtimeResult, previous)
272
+ }
273
+ return read(get, runtimeResult.value)
274
+ }, write)
275
+ }
276
+
277
+ const WritableProto = {
278
+ ...AtomProto,
279
+ [WritableTypeId]: WritableTypeId
280
+ } as const
281
+
282
+ /**
283
+ * @since 1.0.0
284
+ * @category refinements
285
+ */
286
+ export const isWritable = <R, W>(atom: Atom<R>): atom is Writable<R, W> => WritableTypeId in atom
287
+
288
+ /**
289
+ * @since 1.0.0
290
+ * @category constructors
291
+ */
292
+ export const readable = <A>(
293
+ read: (get: Context) => A,
294
+ refresh?: (f: <A>(atom: Atom<A>) => void) => void
295
+ ): Atom<A> => {
296
+ const self = Object.create(AtomProto)
297
+ self.keepAlive = false
298
+ self.lazy = true
299
+ self.read = read
300
+ self.refresh = refresh
301
+ return self
302
+ }
303
+
304
+ /**
305
+ * @since 1.0.0
306
+ * @category constructors
307
+ */
308
+ export const writable = <R, W>(
309
+ read: (get: Context) => R,
310
+ write: (ctx: WriteContext<R>, value: W) => void,
311
+ refresh?: (f: <A>(atom: Atom<A>) => void) => void
312
+ ): Writable<R, W> => {
313
+ const self = Object.create(WritableProto)
314
+ self.keepAlive = false
315
+ self.lazy = true
316
+ self.read = read
317
+ self.write = write
318
+ self.refresh = refresh
319
+ return self
320
+ }
321
+
322
+ function constSetSelf<A>(ctx: WriteContext<A>, value: A) {
323
+ ctx.setSelf(value)
324
+ }
325
+
326
+ // -----------------------------------------------------------------------------
327
+ // constructors
328
+ // -----------------------------------------------------------------------------
329
+
330
+ /**
331
+ * @since 1.0.0
332
+ * @category constructors
333
+ */
334
+ export const make: {
335
+ <A, E>(create: (get: Context) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
336
+ readonly initialValue?: A
337
+ }): Atom<Result.Result<A, E>>
338
+ <A, E>(effect: Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
339
+ readonly initialValue?: A
340
+ }): Atom<Result.Result<A, E>>
341
+ <A, E>(create: (get: Context) => Stream.Stream<A, E, AtomRegistry>, options?: {
342
+ readonly initialValue?: A
343
+ }): Atom<Result.Result<A, E>>
344
+ <A, E>(stream: Stream.Stream<A, E, AtomRegistry>, options?: {
345
+ readonly initialValue?: A
346
+ }): Atom<Result.Result<A, E>>
347
+ <A>(create: (get: Context) => A): Atom<A>
348
+ <A>(initialValue: A): Writable<A>
349
+ } = (arg: any, options?: { readonly initialValue?: unknown }) => {
350
+ const readOrAtom = makeRead(arg, options)
351
+ if (TypeId in readOrAtom) {
352
+ return readOrAtom as any
353
+ }
354
+ return readable(readOrAtom)
355
+ }
356
+
357
+ // -----------------------------------------------------------------------------
358
+ // constructors - effect
359
+ // -----------------------------------------------------------------------------
360
+
361
+ const isDataType = (u: object): u is Option.Option<unknown> | Either.Either<unknown, unknown> =>
362
+ Option.TypeId in u ||
363
+ Either.TypeId in u
364
+
365
+ const makeRead: {
366
+ <A, E>(effect: Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
367
+ readonly initialValue?: A
368
+ }): (get: Context, runtime?: Runtime.Runtime<any>) => Result.Result<A, E>
369
+ <A, E>(create: (get: Context) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
370
+ readonly initialValue?: A
371
+ }): (get: Context, runtime?: Runtime.Runtime<any>) => Result.Result<A, E>
372
+ <A, E>(stream: Stream.Stream<A, E, AtomRegistry>, options?: {
373
+ readonly initialValue?: A
374
+ }): (get: Context, runtime?: Runtime.Runtime<any>) => Result.Result<A, E>
375
+ <A, E>(create: (get: Context) => Stream.Stream<A, E, AtomRegistry>, options?: {
376
+ readonly initialValue?: A
377
+ }): (get: Context, runtime?: Runtime.Runtime<any>) => Result.Result<A, E>
378
+ <A>(create: (get: Context) => A): (get: Context, runtime?: Runtime.Runtime<any>) => A
379
+ <A>(initialValue: A): Writable<A>
380
+ } = <A, E>(
381
+ arg:
382
+ | Effect.Effect<A, E, Scope.Scope | AtomRegistry>
383
+ | ((get: Context) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>)
384
+ | Stream.Stream<A, E, AtomRegistry>
385
+ | ((get: Context) => Stream.Stream<A, E, AtomRegistry>)
386
+ | ((get: Context) => A)
387
+ | A,
388
+ options?: { readonly initialValue?: unknown }
389
+ ) => {
390
+ if (typeof arg === "function") {
391
+ const create = arg as (get: Context) => any
392
+ return function(get: Context, providedRuntime?: Runtime.Runtime<any>) {
393
+ const value = create(get)
394
+ if (typeof value === "object" && value !== null) {
395
+ if (isDataType(value)) {
396
+ return value
397
+ } else if (Effect.EffectTypeId in value) {
398
+ return effect(get, value as any, options, providedRuntime)
399
+ } else if (Stream.StreamTypeId in value) {
400
+ return stream(get, value, options, providedRuntime)
401
+ }
402
+ }
403
+ return value
404
+ }
405
+ } else if (typeof arg === "object" && arg !== null) {
406
+ if (isDataType(arg)) {
407
+ return state(arg)
408
+ } else if (Effect.EffectTypeId in arg) {
409
+ return function(get: Context, providedRuntime?: Runtime.Runtime<any>) {
410
+ return effect(get, arg, options, providedRuntime)
411
+ }
412
+ } else if (Stream.StreamTypeId in arg) {
413
+ return function(get: Context, providedRuntime?: Runtime.Runtime<any>) {
414
+ return stream(get, arg, options, providedRuntime)
415
+ }
416
+ }
417
+ }
418
+
419
+ return state(arg) as any
420
+ }
421
+
422
+ const state = <A>(
423
+ initialValue: A
424
+ ): Writable<A> =>
425
+ writable(function(_get) {
426
+ return initialValue
427
+ }, constSetSelf)
428
+
429
+ const effect = <A, E>(
430
+ get: Context,
431
+ effect: Effect.Effect<A, E, Scope.Scope | AtomRegistry>,
432
+ options?: { readonly initialValue?: A; readonly uninterruptible?: boolean },
433
+ runtime?: Runtime.Runtime<any>
434
+ ): Result.Result<A, E> => {
435
+ const initialValue = options?.initialValue !== undefined
436
+ ? Result.success<A, E>(options.initialValue)
437
+ : Result.initial<A, E>()
438
+ return makeEffect(get, effect, initialValue, runtime, options?.uninterruptible)
439
+ }
440
+
441
+ function makeEffect<A, E>(
442
+ ctx: Context,
443
+ effect: Effect.Effect<A, E, Scope.Scope | AtomRegistry>,
444
+ initialValue: Result.Result<A, E>,
445
+ runtime = Runtime.defaultRuntime,
446
+ uninterruptible = false
447
+ ): Result.Result<A, E> {
448
+ const previous = ctx.self<Result.Result<A, E>>()
449
+
450
+ const scope = Effect.runSync(Scope.make())
451
+ ctx.addFinalizer(() => {
452
+ Effect.runFork(Scope.close(scope, Exit.void))
453
+ })
454
+ const contextMap = new Map(runtime.context.unsafeMap)
455
+ contextMap.set(Scope.Scope.key, scope)
456
+ contextMap.set(AtomRegistry.key, ctx.registry)
457
+ const scopedRuntime = Runtime.make({
458
+ context: EffectContext.unsafeMake(contextMap),
459
+ fiberRefs: runtime.fiberRefs,
460
+ runtimeFlags: runtime.runtimeFlags
461
+ })
462
+ let syncResult: Result.Result<A, E> | undefined
463
+ let isAsync = false
464
+ const cancel = runCallbackSync(scopedRuntime)(
465
+ effect,
466
+ function(exit) {
467
+ syncResult = Result.fromExitWithPrevious(exit, previous)
468
+ if (isAsync) ctx.setSelf(syncResult)
469
+ },
470
+ uninterruptible
471
+ )
472
+ isAsync = true
473
+ if (cancel !== undefined) {
474
+ ctx.addFinalizer(cancel)
475
+ }
476
+ if (syncResult !== undefined) {
477
+ return syncResult
478
+ } else if (previous._tag === "Some") {
479
+ return Result.waitingFrom(previous)
480
+ }
481
+ return Result.waiting(initialValue)
482
+ }
483
+
484
+ // -----------------------------------------------------------------------------
485
+ // context
486
+ // -----------------------------------------------------------------------------
487
+
488
+ /**
489
+ * @since 1.0.0
490
+ * @category models
491
+ */
492
+ export interface AtomRuntime<R, ER> extends Atom<Result.Result<Runtime.Runtime<R>, ER>> {
493
+ readonly layer: Atom<Layer.Layer<R, ER>>
494
+
495
+ readonly atom: {
496
+ <A, E>(create: (get: Context) => Effect.Effect<A, E, Scope.Scope | R | AtomRegistry>, options?: {
497
+ readonly initialValue?: A
498
+ }): Atom<Result.Result<A, E | ER>>
499
+ <A, E>(effect: Effect.Effect<A, E, Scope.Scope | R>, options?: {
500
+ readonly initialValue?: A
501
+ }): Atom<Result.Result<A, E | ER>>
502
+ <A, E>(create: (get: Context) => Stream.Stream<A, E, AtomRegistry | R>, options?: {
503
+ readonly initialValue?: A
504
+ }): Atom<Result.Result<A, E | ER>>
505
+ <A, E>(stream: Stream.Stream<A, E, AtomRegistry | R>, options?: {
506
+ readonly initialValue?: A
507
+ }): Atom<Result.Result<A, E | ER>>
508
+ }
509
+
510
+ readonly fn: {
511
+ <Arg>(): {
512
+ <E, A>(fn: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry | R>, options?: {
513
+ readonly initialValue?: A
514
+ }): AtomResultFn<Arg, A, E | ER>
515
+ <E, A>(fn: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry | R>, options?: {
516
+ readonly initialValue?: A
517
+ }): AtomResultFn<Arg, A, E | ER | NoSuchElementException>
518
+ }
519
+ <E, A, Arg = void>(
520
+ fn: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry | R>,
521
+ options?: {
522
+ readonly initialValue?: A
523
+ }
524
+ ): AtomResultFn<Arg, A, E | ER>
525
+ <E, A, Arg = void>(fn: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry | R>, options?: {
526
+ readonly initialValue?: A
527
+ }): AtomResultFn<Arg, A, E | ER | NoSuchElementException>
528
+ }
529
+
530
+ readonly pull: <A, E>(
531
+ create: ((get: Context) => Stream.Stream<A, E, R | AtomRegistry>) | Stream.Stream<A, E, R | AtomRegistry>,
532
+ options?: {
533
+ readonly disableAccumulation?: boolean
534
+ readonly initialValue?: ReadonlyArray<A>
535
+ }
536
+ ) => Writable<PullResult<A, E | ER>, void>
537
+
538
+ readonly subscriptionRef: <A, E>(
539
+ create:
540
+ | Effect.Effect<SubscriptionRef.SubscriptionRef<A>, E, R | AtomRegistry>
541
+ | ((get: Context) => Effect.Effect<SubscriptionRef.SubscriptionRef<A>, E, R | AtomRegistry>)
542
+ ) => Writable<Result.Result<A, E>, A>
543
+
544
+ readonly subscribable: <A, E, E1 = never>(
545
+ create:
546
+ | Effect.Effect<Subscribable.Subscribable<A, E, R>, E1, R | AtomRegistry>
547
+ | ((get: Context) => Effect.Effect<Subscribable.Subscribable<A, E, R>, E1, R | AtomRegistry>)
548
+ ) => Atom<Result.Result<A, E | E1>>
549
+ }
550
+
551
+ /**
552
+ * @since 1.0.0
553
+ * @category models
554
+ */
555
+ export interface RuntimeFactory {
556
+ <R, E>(
557
+ create: Layer.Layer<R, E, AtomRegistry> | ((get: Context) => Layer.Layer<R, E, AtomRegistry>)
558
+ ): AtomRuntime<R, E>
559
+ readonly memoMap: Layer.MemoMap
560
+ readonly addGlobalLayer: <A, E>(layer: Layer.Layer<A, E, AtomRegistry>) => void
561
+ }
562
+
563
+ /**
564
+ * @since 1.0.0
565
+ * @category constructors
566
+ */
567
+ export const context: (options: {
568
+ readonly memoMap: Layer.MemoMap
569
+ }) => RuntimeFactory = (options) => {
570
+ let globalLayer: Layer.Layer<any, any, AtomRegistry> | undefined
571
+ function factory<E, R>(
572
+ create: Layer.Layer<R, E, AtomRegistry> | ((get: Context) => Layer.Layer<R, E, AtomRegistry>)
573
+ ): AtomRuntime<R, E> {
574
+ const self = Object.create(RuntimeProto)
575
+ self.keepAlive = false
576
+ self.lazy = true
577
+ self.refresh = undefined
578
+
579
+ const layerAtom = keepAlive(
580
+ typeof create === "function"
581
+ ? readable((get) => globalLayer ? Layer.provideMerge(create(get), globalLayer) : create(get))
582
+ : readable(() => globalLayer ? Layer.provideMerge(create, globalLayer) : create)
583
+ )
584
+ self.layer = layerAtom
585
+
586
+ self.read = function read(get: Context) {
587
+ const layer = get(layerAtom)
588
+ const build = Effect.flatMap(
589
+ Effect.flatMap(Effect.scope, (scope) => Layer.buildWithMemoMap(layer, options.memoMap, scope)),
590
+ (context) => Effect.provide(Effect.runtime<R>(), context)
591
+ )
592
+ return effect(get, build, { uninterruptible: true })
593
+ }
594
+
595
+ return self
596
+ }
597
+ factory.memoMap = options.memoMap
598
+ factory.addGlobalLayer = (layer: Layer.Layer<any, any, AtomRegistry>) => {
599
+ if (globalLayer === undefined) {
600
+ globalLayer = layer
601
+ } else {
602
+ globalLayer = Layer.provideMerge(globalLayer, layer)
603
+ }
604
+ }
605
+ return factory
606
+ }
607
+
608
+ /**
609
+ * @since 1.0.0
610
+ * @category context
611
+ */
612
+ export const defaultMemoMap: Layer.MemoMap = globalValue(
613
+ "@effect-atom/atom/Atom/defaultMemoMap",
614
+ () => Effect.runSync(Layer.makeMemoMap)
615
+ )
616
+
617
+ /**
618
+ * @since 1.0.0
619
+ * @category context
620
+ */
621
+ export const runtime: RuntimeFactory = globalValue(
622
+ "@effect-atom/atom/Atom/defaultContext",
623
+ () => context({ memoMap: defaultMemoMap })
624
+ )
625
+
626
+ // -----------------------------------------------------------------------------
627
+ // constructors - stream
628
+ // -----------------------------------------------------------------------------
629
+
630
+ const stream = <A, E>(
631
+ get: Context,
632
+ stream: Stream.Stream<A, E, AtomRegistry>,
633
+ options?: { readonly initialValue?: A },
634
+ runtime?: Runtime.Runtime<any>
635
+ ): Result.Result<A, E | NoSuchElementException> => {
636
+ const initialValue = options?.initialValue !== undefined
637
+ ? Result.success<A, E>(options.initialValue)
638
+ : Result.initial<A, E>()
639
+ return makeStream(get, stream, initialValue, runtime)
640
+ }
641
+
642
+ function makeStream<A, E>(
643
+ ctx: Context,
644
+ stream: Stream.Stream<A, E, AtomRegistry>,
645
+ initialValue: Result.Result<A, E | NoSuchElementException>,
646
+ runtime = Runtime.defaultRuntime
647
+ ): Result.Result<A, E | NoSuchElementException> {
648
+ const previous = ctx.self<Result.Result<A, E | NoSuchElementException>>()
649
+
650
+ const writer: Channel.Channel<never, Chunk.Chunk<A>, never, E> = Channel.readWithCause({
651
+ onInput(input: Chunk.Chunk<A>) {
652
+ return Channel.suspend(() => {
653
+ const last = Chunk.last(input)
654
+ if (last._tag === "Some") {
655
+ ctx.setSelf(Result.success(last.value, {
656
+ waiting: true
657
+ }))
658
+ }
659
+ return writer
660
+ })
661
+ },
662
+ onFailure(cause: Cause.Cause<E>) {
663
+ return Channel.sync(() => {
664
+ ctx.setSelf(Result.failureWithPrevious(cause, { previous }))
665
+ })
666
+ },
667
+ onDone(_done: unknown) {
668
+ return Channel.sync(() => {
669
+ pipe(
670
+ ctx.self<Result.Result<A, E | NoSuchElementException>>(),
671
+ Option.flatMap(Result.value),
672
+ Option.match({
673
+ onNone: () => ctx.setSelf(Result.failWithPrevious(new NoSuchElementException(), { previous })),
674
+ onSome: (a) => ctx.setSelf(Result.success(a))
675
+ })
676
+ )
677
+ })
678
+ }
679
+ })
680
+
681
+ const registryRuntime = Runtime.make({
682
+ context: EffectContext.add(runtime.context, AtomRegistry, ctx.registry),
683
+ fiberRefs: runtime.fiberRefs,
684
+ runtimeFlags: runtime.runtimeFlags
685
+ })
686
+
687
+ const cancel = runCallbackSync(registryRuntime)(
688
+ Channel.runDrain(Channel.pipeTo(Stream.toChannel(stream), writer)),
689
+ constVoid
690
+ )
691
+ if (cancel !== undefined) {
692
+ ctx.addFinalizer(cancel)
693
+ }
694
+
695
+ if (previous._tag === "Some") {
696
+ return Result.waitingFrom(previous)
697
+ }
698
+ return Result.waiting(initialValue)
699
+ }
700
+
701
+ // -----------------------------------------------------------------------------
702
+ // constructors - subscription ref
703
+ // -----------------------------------------------------------------------------
704
+
705
+ /** @internal */
706
+ const readSubscribable = (
707
+ get: Context,
708
+ sub:
709
+ | Subscribable.Subscribable<any, any>
710
+ | Result.Result<Subscribable.Subscribable<any, any>, any>,
711
+ runtime = Runtime.defaultRuntime
712
+ ) => {
713
+ if (Subscribable.TypeId in sub) {
714
+ get.addFinalizer(
715
+ sub.changes.pipe(
716
+ Stream.runForEach((value) => {
717
+ get.setSelf(value)
718
+ return Effect.void
719
+ }),
720
+ Runtime.runCallback(runtime)
721
+ )
722
+ )
723
+ return Runtime.runSync(runtime)(sub.get)
724
+ } else if (sub._tag !== "Success") {
725
+ return sub
726
+ }
727
+ return makeStream(get, sub.value.changes, Result.initial(true), runtime)
728
+ }
729
+
730
+ const makeSubRef = (
731
+ refAtom: Atom<SubscriptionRef.SubscriptionRef<any> | Result.Result<SubscriptionRef.SubscriptionRef<any>, any>>,
732
+ read: (
733
+ get: Context,
734
+ ref: SubscriptionRef.SubscriptionRef<any> | Result.Success<SubscriptionRef.SubscriptionRef<any>, any>
735
+ ) => any
736
+ ) => {
737
+ function write(ctx: WriteContext<SubscriptionRef.SubscriptionRef<any>>, value: any) {
738
+ const ref = ctx.get(refAtom)
739
+ if (SubscriptionRef.SubscriptionRefTypeId in ref) {
740
+ Effect.runSync(SubscriptionRef.set(ref, value))
741
+ } else if (Result.isSuccess(ref)) {
742
+ Effect.runSync(SubscriptionRef.set(ref.value, value))
743
+ }
744
+ }
745
+ return writable((get) => {
746
+ const ref = get(refAtom)
747
+ if (SubscriptionRef.SubscriptionRefTypeId in ref) {
748
+ return read(get, ref)
749
+ } else if (Result.isSuccess(ref)) {
750
+ return read(get, ref)
751
+ }
752
+ return ref
753
+ }, write)
754
+ }
755
+
756
+ /**
757
+ * @since 1.0.0
758
+ * @category constructors
759
+ */
760
+ export const subscriptionRef: {
761
+ <A>(ref: SubscriptionRef.SubscriptionRef<A> | ((get: Context) => SubscriptionRef.SubscriptionRef<A>)): Writable<A>
762
+ <A, E>(
763
+ effect:
764
+ | Effect.Effect<SubscriptionRef.SubscriptionRef<A>, E, Scope.Scope | AtomRegistry>
765
+ | ((get: Context) => Effect.Effect<SubscriptionRef.SubscriptionRef<A>, E, Scope.Scope | AtomRegistry>)
766
+ ): Writable<Result.Result<A, E>, A>
767
+ } = (
768
+ ref:
769
+ | SubscriptionRef.SubscriptionRef<any>
770
+ | ((get: Context) => SubscriptionRef.SubscriptionRef<any>)
771
+ | Effect.Effect<SubscriptionRef.SubscriptionRef<any>, any, Scope.Scope | AtomRegistry>
772
+ | ((get: Context) => Effect.Effect<SubscriptionRef.SubscriptionRef<any>, any, Scope.Scope | AtomRegistry>)
773
+ ) =>
774
+ makeSubRef(
775
+ readable((get) => {
776
+ const value = typeof ref === "function" ? ref(get) : ref
777
+ return SubscriptionRef.SubscriptionRefTypeId in value
778
+ ? value
779
+ : makeEffect(get, value, Result.initial(true))
780
+ }),
781
+ readSubscribable
782
+ )
783
+
784
+ // -----------------------------------------------------------------------------
785
+ // constructors - subscribable
786
+ // -----------------------------------------------------------------------------
787
+
788
+ /**
789
+ * @since 1.0.0
790
+ * @category constructors
791
+ */
792
+ export const subscribable: {
793
+ <A, E>(
794
+ ref: Subscribable.Subscribable<A, E> | ((get: Context) => Subscribable.Subscribable<A, E>)
795
+ ): Atom<A>
796
+ <A, E, E1>(
797
+ effect:
798
+ | Effect.Effect<Subscribable.Subscribable<A, E1>, E, Scope.Scope | AtomRegistry>
799
+ | ((get: Context) => Effect.Effect<Subscribable.Subscribable<A, E1>, E, Scope.Scope | AtomRegistry>)
800
+ ): Atom<Result.Result<A, E | E1>>
801
+ } = (
802
+ ref:
803
+ | Subscribable.Subscribable<any, any>
804
+ | ((get: Context) => Subscribable.Subscribable<any, any>)
805
+ | Effect.Effect<Subscribable.Subscribable<any, any>, any, Scope.Scope | AtomRegistry>
806
+ | ((get: Context) => Effect.Effect<Subscribable.Subscribable<any, any>, any, Scope.Scope | AtomRegistry>)
807
+ ) =>
808
+ makeSubscribable(
809
+ readable((get) => {
810
+ const value = typeof ref === "function" ? ref(get) : ref
811
+ return Subscribable.isSubscribable(value)
812
+ ? value
813
+ : makeEffect(get, value, Result.initial(true))
814
+ }),
815
+ readSubscribable
816
+ )
817
+
818
+ const makeSubscribable = (
819
+ subAtom: Atom<Subscribable.Subscribable<any, any> | Result.Result<Subscribable.Subscribable<any, any>, any>>,
820
+ read: (
821
+ get: Context,
822
+ sub: Subscribable.Subscribable<any, any> | Result.Success<Subscribable.Subscribable<any, any>, any>
823
+ ) => any
824
+ ) =>
825
+ readable((get) => {
826
+ const sub = get(subAtom)
827
+ if (Subscribable.isSubscribable(sub)) {
828
+ return read(get, sub)
829
+ } else if (Result.isSuccess(sub)) {
830
+ return read(get, sub)
831
+ }
832
+ return sub
833
+ })
834
+
835
+ // -----------------------------------------------------------------------------
836
+ // constructors - functions
837
+ // -----------------------------------------------------------------------------
838
+
839
+ /**
840
+ * @since 1.0.0
841
+ * @category models
842
+ */
843
+ export interface FnContext extends Omit<Context, "get" | "once" | "resultOnce" | "someOnce" | "refreshSelf"> {
844
+ <A>(atom: Atom<A>): A
845
+ }
846
+
847
+ /**
848
+ * @since 1.0.0
849
+ * @category constructors
850
+ */
851
+ export const fnSync: {
852
+ <Arg>(): {
853
+ <A>(
854
+ f: (arg: Arg, get: FnContext) => A
855
+ ): Writable<Option.Option<A>, Arg>
856
+ <A>(
857
+ f: (arg: Arg, get: FnContext) => A,
858
+ options: { readonly initialValue: A }
859
+ ): Writable<A, Arg>
860
+ }
861
+ <A, Arg = void>(
862
+ f: (arg: Arg, get: FnContext) => A
863
+ ): Writable<Option.Option<A>, Arg>
864
+ <A, Arg = void>(
865
+ f: (arg: Arg, get: FnContext) => A,
866
+ options: { readonly initialValue: A }
867
+ ): Writable<A, Arg>
868
+ } = function(...args: ReadonlyArray<any>) {
869
+ if (args.length === 0) {
870
+ return makeFnSync
871
+ }
872
+ return makeFnSync(...args as [any, any]) as any
873
+ }
874
+
875
+ const makeFnSync = <Arg, A>(f: (arg: Arg, get: FnContext) => A, options?: {
876
+ readonly initialValue?: A
877
+ }): Writable<Option.Option<A> | A, Arg> => {
878
+ const argAtom = state<[number, Arg]>([0, undefined as any])
879
+ const hasInitialValue = options?.initialValue !== undefined
880
+ return writable(function(get) {
881
+ ;(get as any).isFn = true
882
+ const [counter, arg] = get.get(argAtom)
883
+ if (counter === 0) {
884
+ return hasInitialValue ? options.initialValue : Option.none()
885
+ }
886
+ return hasInitialValue ? f(arg, get) : Option.some(f(arg, get))
887
+ }, function(ctx, arg) {
888
+ batch(() => {
889
+ ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, arg as Arg])
890
+ ctx.refreshSelf()
891
+ })
892
+ })
893
+ }
894
+
895
+ /**
896
+ * @since 1.0.0
897
+ * @category models
898
+ */
899
+ export interface AtomResultFn<Arg, A, E = never> extends Writable<Result.Result<A, E>, Arg | Reset> {}
900
+
901
+ /**
902
+ * @since 1.0.0
903
+ * @category symbols
904
+ */
905
+ export const Reset = Symbol.for("@effect-atom/atom/Atom/Reset")
906
+
907
+ /**
908
+ * @since 1.0.0
909
+ * @category symbols
910
+ */
911
+ export type Reset = typeof Reset
912
+
913
+ /**
914
+ * @since 1.0.0
915
+ * @category constructors
916
+ */
917
+ export const fn: {
918
+ <Arg>(): <E, A>(fn: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
919
+ readonly initialValue?: A
920
+ }) => AtomResultFn<Arg, A, E>
921
+ <E, A, Arg = void>(fn: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry>, options?: {
922
+ readonly initialValue?: A
923
+ }): AtomResultFn<Arg, A, E>
924
+ <Arg>(): <E, A>(fn: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry>, options?: {
925
+ readonly initialValue?: A
926
+ }) => AtomResultFn<Arg, A, E | NoSuchElementException>
927
+ <E, A, Arg = void>(fn: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry>, options?: {
928
+ readonly initialValue?: A
929
+ }): AtomResultFn<Arg, A, E | NoSuchElementException>
930
+ } = function(...args: ReadonlyArray<any>) {
931
+ if (args.length === 0) {
932
+ return makeFn
933
+ }
934
+ return makeFn(...args as [any, any]) as any
935
+ }
936
+
937
+ const makeFn = <Arg, E, A>(
938
+ f: (arg: Arg, get: FnContext) => Stream.Stream<A, E, AtomRegistry> | Effect.Effect<A, E, Scope.Scope | AtomRegistry>,
939
+ options?: {
940
+ readonly initialValue?: A
941
+ }
942
+ ): AtomResultFn<Arg, A, E | NoSuchElementException> => {
943
+ const [read, write] = makeResultFn(f, options)
944
+ return writable(read, write) as any
945
+ }
946
+
947
+ function makeResultFn<Arg, E, A>(
948
+ f: (arg: Arg, get: FnContext) => Effect.Effect<A, E, Scope.Scope | AtomRegistry> | Stream.Stream<A, E, AtomRegistry>,
949
+ options?: { readonly initialValue?: A }
950
+ ) {
951
+ const argAtom = state<[number, Arg]>([0, undefined as any])
952
+ const initialValue = options?.initialValue !== undefined
953
+ ? Result.success<A, E>(options.initialValue)
954
+ : Result.initial<A, E>()
955
+
956
+ function read(get: Context, runtime?: Runtime.Runtime<any>): Result.Result<A, E | NoSuchElementException> {
957
+ ;(get as any).isFn = true
958
+ const [counter, arg] = get.get(argAtom)
959
+ if (counter === 0) {
960
+ return initialValue
961
+ }
962
+ const value = f(arg, get)
963
+ if (Effect.EffectTypeId in value) {
964
+ return makeEffect(get, value, initialValue, runtime)
965
+ }
966
+ return makeStream(get, value, initialValue, runtime)
967
+ }
968
+ function write(ctx: WriteContext<Result.Result<A, E | NoSuchElementException>>, arg: Arg | Reset) {
969
+ batch(() => {
970
+ if (arg === Reset) {
971
+ ctx.set(argAtom, [0, undefined as any])
972
+ } else {
973
+ ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, arg])
974
+ }
975
+ ctx.refreshSelf()
976
+ })
977
+ }
978
+ return [read, write, argAtom] as const
979
+ }
980
+
981
+ /**
982
+ * @since 1.0.0
983
+ * @category models
984
+ */
985
+ export type PullResult<A, E = never> = Result.Result<{
986
+ readonly done: boolean
987
+ readonly items: Arr.NonEmptyArray<A>
988
+ }, E | Cause.NoSuchElementException>
989
+
990
+ /**
991
+ * @since 1.0.0
992
+ * @category constructors
993
+ */
994
+ export const pull = <A, E>(
995
+ create: ((get: Context) => Stream.Stream<A, E, AtomRegistry>) | Stream.Stream<A, E, AtomRegistry>,
996
+ options?: {
997
+ readonly disableAccumulation?: boolean
998
+ }
999
+ ): Writable<PullResult<A, E>, void> => {
1000
+ const pullSignal = state(0)
1001
+ const pullAtom = readable(
1002
+ makeRead(function(get) {
1003
+ return makeStreamPullEffect(get, pullSignal, create, options)
1004
+ })
1005
+ )
1006
+ return makeStreamPull(pullSignal, pullAtom)
1007
+ }
1008
+
1009
+ const makeStreamPullEffect = <A, E>(
1010
+ get: Context,
1011
+ pullSignal: Atom<number>,
1012
+ create: Stream.Stream<A, E, AtomRegistry> | ((get: Context) => Stream.Stream<A, E, AtomRegistry>),
1013
+ options?: {
1014
+ readonly disableAccumulation?: boolean
1015
+ }
1016
+ ): Effect.Effect<
1017
+ { readonly done: boolean; readonly items: Arr.NonEmptyArray<A> },
1018
+ E | Cause.NoSuchElementException,
1019
+ Scope.Scope | AtomRegistry
1020
+ > =>
1021
+ Effect.flatMap(
1022
+ Channel.toPull(
1023
+ Stream.toChannel(typeof create === "function" ? create(get) : create)
1024
+ ),
1025
+ (pullChunk) => {
1026
+ const semaphore = Effect.unsafeMakeSemaphore(1)
1027
+ const fiber = Option.getOrThrow(Fiber.getCurrentFiber())
1028
+ const context = fiber.currentContext as EffectContext.Context<AtomRegistry | Scope.Scope>
1029
+ let acc = Chunk.empty<A>()
1030
+ const pull: Effect.Effect<
1031
+ {
1032
+ done: boolean
1033
+ items: Arr.NonEmptyArray<A>
1034
+ },
1035
+ NoSuchElementException | E,
1036
+ Registry.AtomRegistry
1037
+ > = Effect.flatMap(
1038
+ Effect.locally(
1039
+ Effect.suspend(() => pullChunk),
1040
+ FiberRef.currentContext,
1041
+ context
1042
+ ),
1043
+ Either.match({
1044
+ onLeft: (): Effect.Effect<
1045
+ { done: boolean; items: Arr.NonEmptyArray<A> },
1046
+ NoSuchElementException
1047
+ > => {
1048
+ const items = Chunk.toReadonlyArray(acc) as Array<A>
1049
+ if (!Arr.isNonEmptyArray(items)) {
1050
+ return Effect.fail(new Cause.NoSuchElementException(`Atom.pull: no items`))
1051
+ }
1052
+ return Effect.succeed({ done: true, items })
1053
+ },
1054
+ onRight(chunk) {
1055
+ let items: Chunk.Chunk<A>
1056
+ if (options?.disableAccumulation) {
1057
+ items = chunk
1058
+ } else {
1059
+ items = Chunk.appendAll(acc, chunk)
1060
+ acc = items
1061
+ }
1062
+ const arr = Chunk.toReadonlyArray(items) as Array<A>
1063
+ if (!Arr.isNonEmptyArray(arr)) {
1064
+ return pull
1065
+ }
1066
+ return Effect.succeed({ done: false, items: arr })
1067
+ }
1068
+ })
1069
+ )
1070
+ const pullWithSemaphore = semaphore.withPermits(1)(pull)
1071
+
1072
+ const runCallback = runCallbackSync(Runtime.make({
1073
+ context,
1074
+ fiberRefs: fiber.getFiberRefs(),
1075
+ runtimeFlags: Runtime.defaultRuntime.runtimeFlags
1076
+ }))
1077
+ const cancels = new Set<() => void>()
1078
+ get.addFinalizer(() => {
1079
+ for (const cancel of cancels) cancel()
1080
+ })
1081
+ get.once(pullSignal)
1082
+ get.subscribe(pullSignal, () => {
1083
+ get.setSelf(Result.waitingFrom(get.self<PullResult<A, E>>()))
1084
+ let cancel: (() => void) | undefined
1085
+ // eslint-disable-next-line prefer-const
1086
+ cancel = runCallback(pullWithSemaphore, (exit) => {
1087
+ if (cancel) cancels.delete(cancel)
1088
+ const result = Result.fromExitWithPrevious(exit, get.self())
1089
+ const pending = cancels.size > 0
1090
+ get.setSelf(pending ? Result.waiting(result) : result)
1091
+ })
1092
+ if (cancel) cancels.add(cancel)
1093
+ })
1094
+
1095
+ return pull
1096
+ }
1097
+ )
1098
+
1099
+ const makeStreamPull = <A, E>(
1100
+ pullSignal: Writable<number>,
1101
+ pullAtom: Atom<PullResult<A, E>>
1102
+ ) =>
1103
+ writable(pullAtom.read, function(ctx, _) {
1104
+ ctx.set(pullSignal, ctx.get(pullSignal) + 1)
1105
+ })
1106
+
1107
+ /**
1108
+ * @since 1.0.0
1109
+ * @category constructors
1110
+ */
1111
+ export const family = typeof WeakRef === "undefined" || typeof FinalizationRegistry === "undefined" ?
1112
+ <Arg, T extends object>(
1113
+ f: (arg: Arg) => T
1114
+ ): (arg: Arg) => T => {
1115
+ const atoms = MutableHashMap.empty<Arg, T>()
1116
+ return function(arg) {
1117
+ const atomEntry = MutableHashMap.get(atoms, arg)
1118
+ if (atomEntry._tag === "Some") {
1119
+ return atomEntry.value
1120
+ }
1121
+ const newAtom = f(arg)
1122
+ MutableHashMap.set(atoms, arg, newAtom)
1123
+ return newAtom
1124
+ }
1125
+ } :
1126
+ <Arg, T extends object>(
1127
+ f: (arg: Arg) => T
1128
+ ): (arg: Arg) => T => {
1129
+ const atoms = MutableHashMap.empty<Arg, WeakRef<T>>()
1130
+ const registry = new FinalizationRegistry<Arg>((arg) => {
1131
+ MutableHashMap.remove(atoms, arg)
1132
+ })
1133
+ return function(arg) {
1134
+ const atomEntry = MutableHashMap.get(atoms, arg).pipe(
1135
+ Option.flatMapNullable((ref) => ref.deref())
1136
+ )
1137
+
1138
+ if (atomEntry._tag === "Some") {
1139
+ return atomEntry.value
1140
+ }
1141
+ const newAtom = f(arg)
1142
+ MutableHashMap.set(atoms, arg, new WeakRef(newAtom))
1143
+ registry.register(newAtom, arg)
1144
+ return newAtom
1145
+ }
1146
+ }
1147
+
1148
+ /**
1149
+ * @since 1.0.0
1150
+ * @category combinators
1151
+ */
1152
+ export const withFallback: {
1153
+ <E2, A2>(
1154
+ fallback: Atom<Result.Result<A2, E2>>
1155
+ ): <R extends Atom<Result.Result<any, any>>>(
1156
+ self: R
1157
+ ) => [R] extends [Writable<infer _, infer RW>]
1158
+ ? Writable<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>, RW>
1159
+ : Atom<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>>
1160
+ <R extends Atom<Result.Result<any, any>>, A2, E2>(
1161
+ self: R,
1162
+ fallback: Atom<Result.Result<A2, E2>>
1163
+ ): [R] extends [Writable<infer _, infer RW>]
1164
+ ? Writable<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>, RW>
1165
+ : Atom<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>>
1166
+ } = dual(2, <R extends Atom<Result.Result<any, any>>, A2, E2>(
1167
+ self: R,
1168
+ fallback: Atom<Result.Result<A2, E2>>
1169
+ ): [R] extends [Writable<infer _, infer RW>]
1170
+ ? Writable<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>, RW>
1171
+ : Atom<Result.Result<Result.Result.Success<Type<R>> | A2, Result.Result.Failure<Type<R>> | E2>> =>
1172
+ {
1173
+ function withFallback(get: Context) {
1174
+ const result = get(self)
1175
+ if (result._tag === "Initial") {
1176
+ return Result.waiting(get(fallback))
1177
+ }
1178
+ return result
1179
+ }
1180
+ return isWritable(self)
1181
+ ? writable(
1182
+ withFallback,
1183
+ self.write,
1184
+ self.refresh ?? function(refresh) {
1185
+ refresh(self)
1186
+ }
1187
+ ) as any
1188
+ : readable(
1189
+ withFallback,
1190
+ self.refresh ?? function(refresh) {
1191
+ refresh(self)
1192
+ }
1193
+ ) as any
1194
+ })
1195
+
1196
+ /**
1197
+ * @since 1.0.0
1198
+ * @category combinators
1199
+ */
1200
+ export const keepAlive = <A extends Atom<any>>(self: A): A =>
1201
+ Object.assign(Object.create(Object.getPrototypeOf(self)), {
1202
+ ...self,
1203
+ keepAlive: true
1204
+ })
1205
+
1206
+ /**
1207
+ * Reverts the `keepAlive` behavior of a reactive value, allowing it to be
1208
+ * disposed of when not in use.
1209
+ *
1210
+ * Note that Atom's have this behavior by default.
1211
+ *
1212
+ * @since 1.0.0
1213
+ * @category combinators
1214
+ */
1215
+ export const autoDispose = <A extends Atom<any>>(self: A): A =>
1216
+ Object.assign(Object.create(Object.getPrototypeOf(self)), {
1217
+ ...self,
1218
+ keepAlive: false
1219
+ })
1220
+
1221
+ /**
1222
+ * @since 1.0.0
1223
+ * @category combinators
1224
+ */
1225
+ export const setLazy: {
1226
+ (lazy: boolean): <A extends Atom<any>>(self: A) => A
1227
+ <A extends Atom<any>>(self: A, lazy: boolean): A
1228
+ } = dual(2, <A extends Atom<any>>(self: A, lazy: boolean) =>
1229
+ Object.assign(Object.create(Object.getPrototypeOf(self)), {
1230
+ ...self,
1231
+ lazy
1232
+ }))
1233
+
1234
+ /**
1235
+ * @since 1.0.0
1236
+ * @category combinators
1237
+ */
1238
+ export const withLabel: {
1239
+ (name: string): <A extends Atom<any>>(self: A) => A
1240
+ <A extends Atom<any>>(self: A, name: string): A
1241
+ } = dual<
1242
+ (name: string) => <A extends Atom<any>>(self: A) => A,
1243
+ <A extends Atom<any>>(self: A, name: string) => A
1244
+ >(2, (self, name) =>
1245
+ Object.assign(Object.create(Object.getPrototypeOf(self)), {
1246
+ ...self,
1247
+ label: [name, new Error().stack?.split("\n")[5] ?? ""]
1248
+ }))
1249
+
1250
+ /**
1251
+ * @since 1.0.0
1252
+ * @category combinators
1253
+ */
1254
+ export const setIdleTTL: {
1255
+ (duration: Duration.DurationInput): <A extends Atom<any>>(self: A) => A
1256
+ <A extends Atom<any>>(self: A, duration: Duration.DurationInput): A
1257
+ } = dual<
1258
+ (duration: Duration.DurationInput) => <A extends Atom<any>>(self: A) => A,
1259
+ <A extends Atom<any>>(self: A, duration: Duration.DurationInput) => A
1260
+ >(2, (self, duration) =>
1261
+ Object.assign(Object.create(Object.getPrototypeOf(self)), {
1262
+ ...self,
1263
+ keepAlive: false,
1264
+ idleTTL: Duration.toMillis(duration)
1265
+ }))
1266
+
1267
+ /**
1268
+ * @since 1.0.0
1269
+ * @category combinators
1270
+ */
1271
+ export const initialValue: {
1272
+ <A>(initialValue: A): (self: Atom<A>) => readonly [Atom<A>, A]
1273
+ <A>(self: Atom<A>, initialValue: A): readonly [Atom<A>, A]
1274
+ } = dual<
1275
+ <A>(initialValue: A) => (self: Atom<A>) => readonly [Atom<A>, A],
1276
+ <A>(self: Atom<A>, initialValue: A) => readonly [Atom<A>, A]
1277
+ >(2, (self, initialValue) => [self, initialValue])
1278
+
1279
+ /**
1280
+ * @since 1.0.0
1281
+ * @category combinators
1282
+ */
1283
+ export const transform: {
1284
+ <R extends Atom<any>, B>(
1285
+ f: (get: Context) => B
1286
+ ): (self: R) => [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1287
+ <R extends Atom<any>, B>(
1288
+ self: R,
1289
+ f: (get: Context) => B
1290
+ ): [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1291
+ } = dual(
1292
+ 2,
1293
+ (<A, B>(self: Atom<A>, f: (get: Context) => B): Atom<B> =>
1294
+ isWritable(self)
1295
+ ? writable(
1296
+ f,
1297
+ function(ctx, value) {
1298
+ ctx.set(self, value)
1299
+ },
1300
+ self.refresh ?? function(refresh) {
1301
+ refresh(self)
1302
+ }
1303
+ )
1304
+ : readable(
1305
+ f,
1306
+ self.refresh ?? function(refresh) {
1307
+ refresh(self)
1308
+ }
1309
+ )) as any
1310
+ )
1311
+
1312
+ /**
1313
+ * @since 1.0.0
1314
+ * @category combinators
1315
+ */
1316
+ export const map: {
1317
+ <R extends Atom<any>, B>(
1318
+ f: (_: Type<R>) => B
1319
+ ): (self: R) => [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1320
+ <R extends Atom<any>, B>(
1321
+ self: R,
1322
+ f: (_: Type<R>) => B
1323
+ ): [R] extends [Writable<infer _, infer RW>] ? Writable<B, RW> : Atom<B>
1324
+ } = dual(
1325
+ 2,
1326
+ <A, B>(self: Atom<A>, f: (_: A) => B): Atom<B> => transform(self, (get) => f(get(self)))
1327
+ )
1328
+
1329
+ /**
1330
+ * @since 1.0.0
1331
+ * @category combinators
1332
+ */
1333
+ export const mapResult: {
1334
+ <R extends Atom<Result.Result<any, any>>, B>(
1335
+ f: (_: Result.Result.Success<Type<R>>) => B
1336
+ ): (
1337
+ self: R
1338
+ ) => [R] extends [Writable<infer _, infer RW>] ? Writable<Result.Result<B, Result.Result.Failure<Type<R>>>, RW>
1339
+ : Atom<Result.Result<B, Result.Result.Failure<Type<R>>>>
1340
+ <R extends Atom<Result.Result<any, any>>, B>(
1341
+ self: R,
1342
+ f: (_: Result.Result.Success<Type<R>>) => B
1343
+ ): [R] extends [Writable<infer _, infer RW>] ? Writable<Result.Result<B, Result.Result.Failure<Type<R>>>, RW>
1344
+ : Atom<Result.Result<B, Result.Result.Failure<Type<R>>>>
1345
+ } = dual(2, <R extends Atom<Result.Result<any, any>>, B>(
1346
+ self: R,
1347
+ f: (_: Result.Result.Success<Type<R>>) => B
1348
+ ): [R] extends [Writable<infer _, infer RW>] ? Writable<Result.Result<B, Result.Result.Failure<Type<R>>>, RW>
1349
+ : Atom<Result.Result<B, Result.Result.Failure<Type<R>>>> => map(self, Result.map(f)))
1350
+
1351
+ /**
1352
+ * @since 1.0.0
1353
+ * @category combinators
1354
+ */
1355
+ export const debounce: {
1356
+ (duration: Duration.DurationInput): <A extends Atom<any>>(self: A) => WithoutSerializable<A>
1357
+ <A extends Atom<any>>(self: A, duration: Duration.DurationInput): WithoutSerializable<A>
1358
+ } = dual(
1359
+ 2,
1360
+ <A>(self: Atom<A>, duration: Duration.DurationInput): Atom<A> => {
1361
+ const millis = Duration.toMillis(duration)
1362
+ return transform(self, function(get) {
1363
+ let timeout: number | undefined
1364
+ let value = get.once(self)
1365
+ function update() {
1366
+ timeout = undefined
1367
+ get.setSelf(value)
1368
+ }
1369
+ get.addFinalizer(function() {
1370
+ if (timeout) clearTimeout(timeout)
1371
+ })
1372
+ get.subscribe(self, function(val) {
1373
+ value = val
1374
+ if (timeout) clearTimeout(timeout)
1375
+ timeout = setTimeout(update, millis)
1376
+ })
1377
+ return value
1378
+ })
1379
+ }
1380
+ )
1381
+
1382
+ /**
1383
+ * @since 1.0.0
1384
+ * @category Optimistic
1385
+ */
1386
+ export const optimistic = <A>(
1387
+ self: Atom<A>
1388
+ ): Writable<A, Atom<Result.Result<A, unknown>>> => {
1389
+ let counter = 0
1390
+ const writeAtom = state(
1391
+ [
1392
+ counter,
1393
+ undefined as any as Atom<Result.Result<A, unknown>>
1394
+ ] as const
1395
+ )
1396
+ return writable(
1397
+ (get) => {
1398
+ let lastValue = get.once(self)
1399
+ get.subscribe(self, (value) => {
1400
+ lastValue = value
1401
+ if (!Result.isResult(value)) {
1402
+ return get.setSelf(value)
1403
+ }
1404
+ const current = Option.getOrUndefined(get.self<Result.Result<any, any>>())!
1405
+ if (Result.isSuccess(current) && Result.isSuccess(value)) {
1406
+ if (value.timestamp >= current.timestamp) {
1407
+ get.setSelf(value)
1408
+ }
1409
+ } else {
1410
+ get.setSelf(value)
1411
+ }
1412
+ })
1413
+ const transitions = new Set<Atom<Result.Result<A, unknown>>>()
1414
+ const cancels = new Set<() => void>()
1415
+ get.subscribe(writeAtom, ([, atom]) => {
1416
+ if (transitions.has(atom)) return
1417
+ transitions.add(atom)
1418
+ let cancel: (() => void) | undefined
1419
+ // eslint-disable-next-line prefer-const
1420
+ cancel = get.registry.subscribe(atom, (result) => {
1421
+ if (Result.isSuccess(result) && result.waiting) {
1422
+ return get.setSelf(result.value)
1423
+ }
1424
+ transitions.delete(atom)
1425
+ if (cancel) {
1426
+ cancels.delete(cancel)
1427
+ cancel()
1428
+ }
1429
+ if (transitions.size === 0) {
1430
+ if (Result.isFailure(result)) {
1431
+ get.setSelf(lastValue)
1432
+ }
1433
+ get.refresh(self)
1434
+ }
1435
+ }, { immediate: true })
1436
+ if (transitions.has(atom)) {
1437
+ cancels.add(cancel)
1438
+ } else {
1439
+ cancel()
1440
+ }
1441
+ })
1442
+ get.addFinalizer(() => {
1443
+ for (const cancel of cancels) cancel()
1444
+ transitions.clear()
1445
+ cancels.clear()
1446
+ })
1447
+ return lastValue
1448
+ },
1449
+ (ctx, atom) => ctx.set(writeAtom, [++counter, atom]),
1450
+ (refresh) => refresh(self)
1451
+ )
1452
+ }
1453
+
1454
+ /**
1455
+ * @since 1.0.0
1456
+ * @category Optimistic
1457
+ */
1458
+ export const optimisticFn: {
1459
+ <A, W, XA, XE, OW = void>(
1460
+ options: {
1461
+ readonly reducer: (current: NoInfer<A>, update: OW) => NoInfer<W>
1462
+ readonly fn:
1463
+ | AtomResultFn<OW, XA, XE>
1464
+ | ((set: (result: NoInfer<W>) => void) => AtomResultFn<OW, XA, XE>)
1465
+ }
1466
+ ): (
1467
+ self: Writable<A, Atom<Result.Result<W, unknown>>>
1468
+ ) => AtomResultFn<OW, XA, XE>
1469
+ <A, W, XA, XE, OW = void>(
1470
+ self: Writable<A, Atom<Result.Result<W, unknown>>>,
1471
+ options: {
1472
+ readonly reducer: (current: NoInfer<A>, update: OW) => NoInfer<W>
1473
+ readonly fn:
1474
+ | AtomResultFn<OW, XA, XE>
1475
+ | ((set: (result: NoInfer<W>) => void) => AtomResultFn<OW, XA, XE>)
1476
+ }
1477
+ ): AtomResultFn<OW, XA, XE>
1478
+ } = dual(2, <A, W, XA, XE, OW = void>(
1479
+ self: Writable<A, Atom<Result.Result<W, unknown>>>,
1480
+ options: {
1481
+ readonly reducer: (current: NoInfer<A>, update: OW) => NoInfer<W>
1482
+ readonly fn:
1483
+ | AtomResultFn<OW, XA, XE>
1484
+ | ((set: (result: NoInfer<W>) => void) => AtomResultFn<OW, XA, XE>)
1485
+ }
1486
+ ): AtomResultFn<OW, XA, XE> => {
1487
+ const transition = state<Result.Result<W, unknown>>(Result.initial())
1488
+ return fn((arg: OW, get) => {
1489
+ let value = options.reducer(get(self), arg)
1490
+ if (Result.isResult(value) && !value.waiting) {
1491
+ value = Result.waiting(value)
1492
+ }
1493
+ get.set(transition, Result.success(value, { waiting: true }))
1494
+ get.set(self, transition)
1495
+ const fn = typeof options.fn === "function"
1496
+ ? autoDispose(options.fn((value) =>
1497
+ get.set(
1498
+ transition,
1499
+ Result.success(Result.isResult(value) ? Result.waiting(value) : value, { waiting: true })
1500
+ )
1501
+ ))
1502
+ : options.fn
1503
+ get.set(fn, arg)
1504
+ return Effect.onExit(get.result(fn, { suspendOnWaiting: true }), (exit) => {
1505
+ get.set(transition, Result.fromExit(Exit.as(exit, value)))
1506
+ return Effect.void
1507
+ })
1508
+ })
1509
+ })
1510
+
1511
+ /**
1512
+ * @since 1.0.0
1513
+ * @category batching
1514
+ */
1515
+ export const batch: (f: () => void) => void = internalRegistry.batch
1516
+
1517
+ // -----------------------------------------------------------------------------
1518
+ // Focus
1519
+ // -----------------------------------------------------------------------------
1520
+
1521
+ /**
1522
+ * @since 1.0.0
1523
+ * @category Focus
1524
+ */
1525
+ export const windowFocusSignal: Atom<number> = readable((get) => {
1526
+ let count = 0
1527
+ function update() {
1528
+ if (document.visibilityState === "visible") {
1529
+ get.setSelf(++count)
1530
+ }
1531
+ }
1532
+ window.addEventListener("visibilitychange", update)
1533
+ get.addFinalizer(() => {
1534
+ window.removeEventListener("visibilitychange", update)
1535
+ })
1536
+ return count
1537
+ })
1538
+
1539
+ /**
1540
+ * @since 1.0.0
1541
+ * @category Focus
1542
+ */
1543
+ export const makeRefreshOnSignal = <_>(signal: Atom<_>) => <A extends Atom<any>>(self: A): WithoutSerializable<A> =>
1544
+ transform(self, (get) => {
1545
+ get.subscribe(signal, (_) => get.refresh(self))
1546
+ get.subscribe(self, (value) => get.setSelf(value))
1547
+ return get.once(self)
1548
+ }) as any
1549
+
1550
+ /**
1551
+ * @since 1.0.0
1552
+ * @category Focus
1553
+ */
1554
+ export const refreshOnWindowFocus: <A extends Atom<any>>(self: A) => WithoutSerializable<A> = makeRefreshOnSignal(
1555
+ windowFocusSignal
1556
+ )
1557
+
1558
+ // -----------------------------------------------------------------------------
1559
+ // KeyValueStore
1560
+ // -----------------------------------------------------------------------------
1561
+
1562
+ /**
1563
+ * @since 1.0.0
1564
+ * @category KeyValueStore
1565
+ */
1566
+ export const kvs = <A>(options: {
1567
+ readonly runtime: AtomRuntime<KeyValueStore.KeyValueStore, any>
1568
+ readonly key: string
1569
+ readonly schema: Schema.Schema<A, any>
1570
+ readonly defaultValue: LazyArg<A>
1571
+ }): Writable<A> => {
1572
+ const setAtom = options.runtime.fn(
1573
+ Effect.fnUntraced(function*(value: A) {
1574
+ const store = (yield* KeyValueStore.KeyValueStore).forSchema(
1575
+ options.schema
1576
+ )
1577
+ yield* store.set(options.key, value)
1578
+ })
1579
+ )
1580
+ const resultAtom = options.runtime.atom(
1581
+ Effect.flatMap(
1582
+ KeyValueStore.KeyValueStore,
1583
+ (store) => Effect.flatten(store.forSchema(options.schema).get(options.key))
1584
+ )
1585
+ )
1586
+ return writable(
1587
+ (get) => {
1588
+ get.mount(setAtom)
1589
+ return Result.getOrElse(get(resultAtom), options.defaultValue)
1590
+ },
1591
+ (ctx, value: A) => {
1592
+ ctx.set(setAtom, value as any)
1593
+ ctx.setSelf(value)
1594
+ }
1595
+ )
1596
+ }
1597
+
1598
+ // -----------------------------------------------------------------------------
1599
+ // URL search params
1600
+ // -----------------------------------------------------------------------------
1601
+
1602
+ /**
1603
+ * Create an Atom that reads and writes a URL search parameter.
1604
+ *
1605
+ * Note: If you pass a schema, it has to be synchronous and have no context.
1606
+ *
1607
+ * @since 1.0.0
1608
+ * @category URL search params
1609
+ */
1610
+ export const searchParam = <A = never, I extends string = never>(name: string, options?: {
1611
+ readonly schema?: Schema.Schema<A, I>
1612
+ }): Writable<[A] extends [never] ? string : Option.Option<A>> => {
1613
+ const decode = options?.schema && Schema.decodeEither(options.schema)
1614
+ const encode = options?.schema && Schema.encodeEither(options.schema)
1615
+ return writable(
1616
+ (get) => {
1617
+ const handleUpdate = () => {
1618
+ if (searchParamState.updating) return
1619
+ const searchParams = new URLSearchParams(window.location.search)
1620
+ const newValue = searchParams.get(name) || ""
1621
+ if (decode) {
1622
+ get.setSelf(Either.getRight(decode(newValue as I)))
1623
+ } else if (newValue !== Option.getOrUndefined(get.self())) {
1624
+ get.setSelf(newValue)
1625
+ }
1626
+ }
1627
+ window.addEventListener("popstate", handleUpdate)
1628
+ window.addEventListener("pushstate", handleUpdate)
1629
+ get.addFinalizer(() => {
1630
+ window.removeEventListener("popstate", handleUpdate)
1631
+ window.removeEventListener("pushstate", handleUpdate)
1632
+ })
1633
+ const value = new URLSearchParams(window.location.search).get(name) || ""
1634
+ return decode ? Either.getRight(decode(value as I)) : value as any
1635
+ },
1636
+ (ctx, value: any) => {
1637
+ if (encode) {
1638
+ const encoded = Option.flatMap(value, (v) => Either.getRight(encode(v as A)))
1639
+ searchParamState.updates.set(name, Option.getOrElse(encoded, () => ""))
1640
+ value = Option.zipRight(encoded, value)
1641
+ } else {
1642
+ searchParamState.updates.set(name, value)
1643
+ }
1644
+ ctx.setSelf(value)
1645
+ if (searchParamState.timeout) {
1646
+ clearTimeout(searchParamState.timeout)
1647
+ }
1648
+ searchParamState.timeout = setTimeout(updateSearchParams, 500)
1649
+ }
1650
+ )
1651
+ }
1652
+
1653
+ const searchParamState = {
1654
+ timeout: undefined as number | undefined,
1655
+ updates: new Map<string, string>(),
1656
+ updating: false
1657
+ }
1658
+
1659
+ function updateSearchParams() {
1660
+ searchParamState.timeout = undefined
1661
+ searchParamState.updating = true
1662
+ const searchParams = new URLSearchParams(window.location.search)
1663
+ for (const [key, value] of searchParamState.updates.entries()) {
1664
+ if (value.length > 0) {
1665
+ searchParams.set(key, value)
1666
+ } else {
1667
+ searchParams.delete(key)
1668
+ }
1669
+ }
1670
+ searchParamState.updates.clear()
1671
+ const newUrl = `${window.location.pathname}?${searchParams.toString()}`
1672
+ window.history.pushState({}, "", newUrl)
1673
+ searchParamState.updating = false
1674
+ }
1675
+
1676
+ // -----------------------------------------------------------------------------
1677
+ // conversions
1678
+ // -----------------------------------------------------------------------------
1679
+
1680
+ /**
1681
+ * @since 1.0.0
1682
+ * @category Conversions
1683
+ */
1684
+ export const toStream = <A>(self: Atom<A>): Stream.Stream<A, never, AtomRegistry> =>
1685
+ Stream.unwrap(Effect.map(AtomRegistry, Registry.toStream(self)))
1686
+
1687
+ /**
1688
+ * @since 1.0.0
1689
+ * @category Conversions
1690
+ */
1691
+ export const toStreamResult = <A, E>(self: Atom<Result.Result<A, E>>): Stream.Stream<A, E, AtomRegistry> =>
1692
+ Stream.unwrap(Effect.map(AtomRegistry, Registry.toStreamResult(self)))
1693
+
1694
+ /**
1695
+ * @since 1.0.0
1696
+ * @category Conversions
1697
+ */
1698
+ export const get = <A>(self: Atom<A>): Effect.Effect<A, never, AtomRegistry> =>
1699
+ Effect.map(AtomRegistry, (_) => _.get(self))
1700
+
1701
+ /**
1702
+ * @since 1.0.0
1703
+ * @category Conversions
1704
+ */
1705
+ export const modify: {
1706
+ <R, W, A>(
1707
+ f: (_: R) => [returnValue: A, nextValue: W]
1708
+ ): (self: Writable<R, W>) => Effect.Effect<A, never, AtomRegistry>
1709
+ <R, W, A>(self: Writable<R, W>, f: (_: R) => [returnValue: A, nextValue: W]): Effect.Effect<A, never, AtomRegistry>
1710
+ } = dual(
1711
+ 2,
1712
+ <R, W, A>(self: Writable<R, W>, f: (_: R) => [returnValue: A, nextValue: W]): Effect.Effect<A, never, AtomRegistry> =>
1713
+ Effect.map(AtomRegistry, (_) => _.modify(self, f))
1714
+ )
1715
+
1716
+ /**
1717
+ * @since 1.0.0
1718
+ * @category Conversions
1719
+ */
1720
+ export const set: {
1721
+ <W>(value: W): <R>(self: Writable<R, W>) => Effect.Effect<void, never, AtomRegistry>
1722
+ <R, W>(self: Writable<R, W>, value: W): Effect.Effect<void, never, AtomRegistry>
1723
+ } = dual(
1724
+ 2,
1725
+ <R, W>(self: Writable<R, W>, value: W): Effect.Effect<void, never, AtomRegistry> =>
1726
+ Effect.map(AtomRegistry, (_) => _.set(self, value))
1727
+ )
1728
+
1729
+ /**
1730
+ * @since 1.0.0
1731
+ * @category Conversions
1732
+ */
1733
+ export const update: {
1734
+ <R, W>(f: (_: R) => W): (self: Writable<R, W>) => Effect.Effect<void, never, AtomRegistry>
1735
+ <R, W>(self: Writable<R, W>, f: (_: R) => W): Effect.Effect<void, never, AtomRegistry>
1736
+ } = dual(
1737
+ 2,
1738
+ <R, W>(self: Writable<R, W>, f: (_: R) => W): Effect.Effect<void, never, AtomRegistry> =>
1739
+ Effect.map(AtomRegistry, (_) => _.update(self, f))
1740
+ )
1741
+
1742
+ /**
1743
+ * @since 1.0.0
1744
+ * @category Conversions
1745
+ */
1746
+ export const getResult = <A, E>(
1747
+ self: Atom<Result.Result<A, E>>,
1748
+ options?: { readonly suspendOnWaiting?: boolean | undefined }
1749
+ ): Effect.Effect<A, E, AtomRegistry> => Effect.flatMap(AtomRegistry, Registry.getResult(self, options))
1750
+
1751
+ /**
1752
+ * @since 1.0.0
1753
+ * @category Conversions
1754
+ */
1755
+ export const refresh = <A>(self: Atom<A>): Effect.Effect<void, never, AtomRegistry> =>
1756
+ Effect.map(AtomRegistry, (_) => _.refresh(self))
1757
+
1758
+ // -----------------------------------------------------------------------------
1759
+ // Serializable
1760
+ // -----------------------------------------------------------------------------
1761
+
1762
+ /**
1763
+ * @since 1.0.0
1764
+ * @category Serializable
1765
+ */
1766
+ export const SerializableTypeId: SerializableTypeId = "~effect-atom/atom/Atom/Serializable"
1767
+
1768
+ /**
1769
+ * @since 1.0.0
1770
+ * @category Serializable
1771
+ */
1772
+ export type SerializableTypeId = "~effect-atom/atom/Atom/Serializable"
1773
+
1774
+ /**
1775
+ * @since 1.0.0
1776
+ * @category Serializable
1777
+ */
1778
+ export interface Serializable {
1779
+ readonly [SerializableTypeId]: {
1780
+ readonly key: string
1781
+ readonly encode: (value: unknown) => unknown
1782
+ readonly decode: (value: unknown) => unknown
1783
+ }
1784
+ }
1785
+
1786
+ /**
1787
+ * @since 1.0.0
1788
+ * @category Serializable
1789
+ */
1790
+ export const isSerializable = (self: Atom<any>): self is Atom<any> & Serializable => SerializableTypeId in self
1791
+
1792
+ /**
1793
+ * @since 1.0.0
1794
+ * @category combinators
1795
+ */
1796
+ export const serializable: {
1797
+ <R extends Atom<any>, I>(options: {
1798
+ readonly key: string
1799
+ readonly schema: Schema.Schema<Type<R>, I>
1800
+ }): (self: R) => R & Serializable
1801
+ <R extends Atom<any>, I>(self: R, options: {
1802
+ readonly key: string
1803
+ readonly schema: Schema.Schema<Type<R>, I>
1804
+ }): R & Serializable
1805
+ } = dual(2, <R extends Atom<any>, A, I>(self: R, options: {
1806
+ readonly key: string
1807
+ readonly schema: Schema.Schema<A, I>
1808
+ }): R & Serializable =>
1809
+ Object.assign(Object.create(Object.getPrototypeOf(self)), {
1810
+ ...self,
1811
+ label: self.label ?? [options.key, new Error().stack?.split("\n")[5] ?? ""],
1812
+ [SerializableTypeId]: {
1813
+ key: options.key,
1814
+ encode: Schema.encodeSync(options.schema),
1815
+ decode: Schema.decodeSync(options.schema)
1816
+ }
1817
+ }))
1818
+
1819
+ /**
1820
+ * @since 1.0.0
1821
+ * @category ServerValue
1822
+ */
1823
+ export const ServerValueTypeId = "~effect-atom/atom/Atom/ServerValue" as const
1824
+
1825
+ /**
1826
+ * Overrides the value of an Atom when read on the server.
1827
+ *
1828
+ * @since 1.0.0
1829
+ * @category ServerValue
1830
+ */
1831
+ export const withServerValue: {
1832
+ <A extends Atom<any>>(read: (get: <A>(atom: Atom<A>) => A) => Type<A>): (self: A) => A
1833
+ <A extends Atom<any>>(self: A, read: (get: <A>(atom: Atom<A>) => A) => Type<A>): A
1834
+ } = dual(
1835
+ 2,
1836
+ <A extends Atom<any>>(self: A, read: (get: <A>(atom: Atom<A>) => A) => Type<A>): A =>
1837
+ Object.assign(Object.create(Object.getPrototypeOf(self)), {
1838
+ ...self,
1839
+ [ServerValueTypeId]: read
1840
+ })
1841
+ )
1842
+
1843
+ /**
1844
+ * Sets the Atom's server value to `Result.initial(true)`.
1845
+ *
1846
+ * @since 1.0.0
1847
+ * @category ServerValue
1848
+ */
1849
+ export const withServerValueInitial = <A extends Atom<Result.Result<any, any>>>(self: A): A =>
1850
+ withServerValue(self, constant(Result.initial(true)) as any)
1851
+
1852
+ /**
1853
+ * @since 1.0.0
1854
+ * @category ServerValue
1855
+ */
1856
+ export const getServerValue: {
1857
+ (registry: Registry.Registry): <A>(self: Atom<A>) => A
1858
+ <A>(self: Atom<A>, registry: Registry.Registry): A
1859
+ } = dual(
1860
+ 2,
1861
+ <A>(self: Atom<A>, registry: Registry.Registry): A =>
1862
+ ServerValueTypeId in self
1863
+ ? (self as any)[ServerValueTypeId]((atom: Atom<any>) => registry.get(atom))
1864
+ : registry.get(self)
1865
+ )