@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/AtomRef.ts ADDED
@@ -0,0 +1,282 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as Equal from "effect/Equal"
5
+ import { globalValue } from "effect/GlobalValue"
6
+ import * as Hash from "effect/Hash"
7
+
8
+ /**
9
+ * @since 1.0.0
10
+ * @category type ids
11
+ */
12
+ export const TypeId: TypeId = "~effect-atom/atom/AtomRef"
13
+
14
+ /**
15
+ * @since 1.0.0
16
+ * @category type ids
17
+ */
18
+ export type TypeId = "~effect-atom/atom/AtomRef"
19
+
20
+ /**
21
+ * @since 1.0.0
22
+ * @category models
23
+ */
24
+ export interface ReadonlyRef<A> extends Equal.Equal {
25
+ readonly [TypeId]: TypeId
26
+ readonly key: string
27
+ readonly value: A
28
+ readonly subscribe: (f: (a: A) => void) => () => void
29
+ readonly map: <B>(f: (a: A) => B) => ReadonlyRef<B>
30
+ }
31
+
32
+ /**
33
+ * @since 1.0.0
34
+ * @category models
35
+ */
36
+ export interface AtomRef<A> extends ReadonlyRef<A> {
37
+ readonly prop: <K extends keyof A>(prop: K) => AtomRef<A[K]>
38
+ readonly set: (value: A) => AtomRef<A>
39
+ readonly update: (f: (value: A) => A) => AtomRef<A>
40
+ }
41
+
42
+ /**
43
+ * @since 1.0.0
44
+ * @category models
45
+ */
46
+ export interface Collection<A> extends ReadonlyRef<ReadonlyArray<AtomRef<A>>> {
47
+ readonly push: (item: A) => Collection<A>
48
+ readonly insertAt: (index: number, item: A) => Collection<A>
49
+ readonly remove: (ref: AtomRef<A>) => Collection<A>
50
+ readonly toArray: () => Array<A>
51
+ }
52
+
53
+ /**
54
+ * @since 1.0.0
55
+ * @category constructors
56
+ */
57
+ export const make = <A>(value: A): AtomRef<A> => new AtomRefImpl(value)
58
+
59
+ /**
60
+ * @since 1.0.0
61
+ * @category constructors
62
+ */
63
+ export const collection = <A>(items: Iterable<A>): Collection<A> => new CollectionImpl(items)
64
+
65
+ const keyState = globalValue("@effect-atom/atom/AtomRef/keyState", () => ({
66
+ count: 0,
67
+ generate() {
68
+ return `AtomRef-${this.count++}`
69
+ }
70
+ }))
71
+
72
+ class ReadonlyRefImpl<A> implements ReadonlyRef<A> {
73
+ readonly [TypeId]: TypeId
74
+ readonly key = keyState.generate()
75
+ constructor(public value: A) {
76
+ this[TypeId] = TypeId
77
+ }
78
+
79
+ [Equal.symbol](that: Equal.Equal) {
80
+ return Equal.equals(this.value, (that as ReadonlyRef<A>).value)
81
+ }
82
+
83
+ [Hash.symbol]() {
84
+ return Hash.hash(this.value)
85
+ }
86
+
87
+ listeners: Array<(a: A) => void> = []
88
+ listenerCount = 0
89
+
90
+ notify(a: A) {
91
+ for (let i = 0; i < this.listenerCount; i++) {
92
+ this.listeners[i](a)
93
+ }
94
+ }
95
+
96
+ subscribe(f: (a: A) => void): () => void {
97
+ this.listeners.push(f)
98
+ this.listenerCount++
99
+
100
+ return () => {
101
+ const index = this.listeners.indexOf(f)
102
+ if (index !== -1) {
103
+ this.listeners[index] = this.listeners[this.listenerCount - 1]
104
+ this.listeners.pop()
105
+ this.listenerCount--
106
+ }
107
+ }
108
+ }
109
+
110
+ map<B>(f: (a: A) => B): ReadonlyRef<B> {
111
+ return new MapRefImpl(this, f)
112
+ }
113
+ }
114
+
115
+ class AtomRefImpl<A> extends ReadonlyRefImpl<A> implements AtomRef<A> {
116
+ prop<K extends keyof A>(prop: K): AtomRef<A[K]> {
117
+ return new PropRefImpl(this, prop)
118
+ }
119
+ set(value: A) {
120
+ if (Equal.equals(value, this.value)) {
121
+ return this
122
+ }
123
+ this.value = value
124
+ this.notify(value)
125
+ return this
126
+ }
127
+
128
+ update(f: (value: A) => A) {
129
+ return this.set(f(this.value))
130
+ }
131
+ }
132
+
133
+ class MapRefImpl<A, B> implements ReadonlyRef<B> {
134
+ readonly [TypeId]: TypeId
135
+ readonly key = keyState.generate()
136
+ constructor(readonly parent: ReadonlyRef<A>, readonly transform: (a: A) => B) {
137
+ this[TypeId] = TypeId
138
+ }
139
+ [Equal.symbol](that: Equal.Equal) {
140
+ return Equal.equals(this.value, (that as ReadonlyRef<B>).value)
141
+ }
142
+ [Hash.symbol]() {
143
+ return Hash.hash(this.value)
144
+ }
145
+ get value() {
146
+ return this.transform(this.parent.value)
147
+ }
148
+ subscribe(f: (a: B) => void): () => void {
149
+ let previous = this.transform(this.parent.value)
150
+ return this.parent.subscribe((a) => {
151
+ const next = this.transform(a)
152
+ if (Equal.equals(next, previous)) {
153
+ return
154
+ }
155
+ previous = next
156
+ f(next)
157
+ })
158
+ }
159
+ map<C>(f: (a: B) => C): ReadonlyRef<C> {
160
+ return new MapRefImpl(this, f)
161
+ }
162
+ }
163
+
164
+ class PropRefImpl<A, K extends keyof A> implements AtomRef<A[K]> {
165
+ readonly [TypeId]: TypeId
166
+ readonly key = keyState.generate()
167
+ private previous: A[K]
168
+ constructor(readonly parent: AtomRef<A>, readonly _prop: K) {
169
+ this[TypeId] = TypeId
170
+ this.previous = parent.value[_prop]
171
+ }
172
+ [Equal.symbol](that: Equal.Equal) {
173
+ return Equal.equals(this.value, (that as ReadonlyRef<A>).value)
174
+ }
175
+ [Hash.symbol]() {
176
+ return Hash.hash(this.value)
177
+ }
178
+ get value() {
179
+ if (this.parent.value && this._prop in (this.parent.value as any)) {
180
+ this.previous = this.parent.value[this._prop]
181
+ }
182
+ return this.previous
183
+ }
184
+ subscribe(f: (a: A[K]) => void): () => void {
185
+ let previous = this.value
186
+ return this.parent.subscribe((a) => {
187
+ if (!a || !(this._prop in (a as any))) {
188
+ return
189
+ }
190
+ const next = a[this._prop]
191
+ if (Equal.equals(next, previous)) {
192
+ return
193
+ }
194
+ previous = next
195
+ f(next)
196
+ })
197
+ }
198
+ map<C>(f: (a: A[K]) => C): ReadonlyRef<C> {
199
+ return new MapRefImpl(this, f)
200
+ }
201
+ prop<CK extends keyof A[K]>(prop: CK): AtomRef<A[K][CK]> {
202
+ return new PropRefImpl(this, prop)
203
+ }
204
+ set(value: A[K]): AtomRef<A[K]> {
205
+ if (Array.isArray(this.parent.value)) {
206
+ const newArray = this.parent.value.slice()
207
+ newArray[this._prop as number] = value
208
+ this.parent.set(newArray as A)
209
+ } else {
210
+ this.parent.set({
211
+ ...this.parent.value,
212
+ [this._prop]: value
213
+ })
214
+ }
215
+ return this
216
+ }
217
+ update(f: (value: A[K]) => A[K]): AtomRef<A[K]> {
218
+ if (Array.isArray(this.parent.value)) {
219
+ const newArray = this.parent.value.slice()
220
+ newArray[this._prop as number] = f(this.parent.value[this._prop])
221
+ this.parent.set(newArray as A)
222
+ } else {
223
+ this.parent.set({
224
+ ...this.parent.value,
225
+ [this._prop]: f(this.parent.value[this._prop])
226
+ })
227
+ }
228
+ return this
229
+ }
230
+ }
231
+
232
+ class CollectionImpl<A> extends ReadonlyRefImpl<Array<AtomRef<A>>> implements Collection<A> {
233
+ constructor(items: Iterable<A>) {
234
+ super([])
235
+ for (const item of items) {
236
+ this.value.push(this.makeRef(item))
237
+ }
238
+ }
239
+
240
+ makeRef(value: A) {
241
+ const ref = new AtomRefImpl(value)
242
+ const notify = (value: A) => {
243
+ ref.notify(value)
244
+ this.notify(this.value)
245
+ }
246
+ return new Proxy(ref, {
247
+ get(target, p, _receiver) {
248
+ if (p === "notify") {
249
+ return notify
250
+ }
251
+ return target[p as keyof AtomRef<A>]
252
+ }
253
+ })
254
+ }
255
+
256
+ push(item: A) {
257
+ const ref = this.makeRef(item)
258
+ this.value.push(ref)
259
+ this.notify(this.value)
260
+ return this
261
+ }
262
+
263
+ insertAt(index: number, item: A) {
264
+ const ref = this.makeRef(item)
265
+ this.value.splice(index, 0, ref)
266
+ this.notify(this.value)
267
+ return this
268
+ }
269
+
270
+ remove(ref: AtomRef<A>) {
271
+ const index = this.value.indexOf(ref)
272
+ if (index !== -1) {
273
+ this.value.splice(index, 1)
274
+ this.notify(this.value)
275
+ }
276
+ return this
277
+ }
278
+
279
+ toArray() {
280
+ return this.value.map((ref) => ref.value)
281
+ }
282
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as Arr from "effect/Array"
5
+ import * as Atom from "./Atom.js"
6
+ import type * as Registry from "./Registry.js"
7
+ import * as Result from "./Result.js"
8
+
9
+ /**
10
+ * @since 1.0.0
11
+ * @category models
12
+ */
13
+ export interface DehydratedAtom {
14
+ readonly key: string
15
+ readonly value: unknown
16
+ readonly dehydratedAt: number
17
+ readonly resultPromise?: Promise<unknown> | undefined
18
+ }
19
+
20
+ /**
21
+ * @since 1.0.0
22
+ * @category dehydration
23
+ */
24
+ export const dehydrate = (
25
+ registry: Registry.Registry,
26
+ options?: {
27
+ /**
28
+ * How to encode `Result.Initial` values. Default is "ignore".
29
+ */
30
+ readonly encodeInitialAs?: "ignore" | "promise" | "value-only" | undefined
31
+ }
32
+ ): Array<DehydratedAtom> => {
33
+ const encodeInitialResultMode = options?.encodeInitialAs ?? "ignore"
34
+ const arr = Arr.empty<DehydratedAtom>()
35
+ const now = Date.now()
36
+ registry.getNodes().forEach((node, key) => {
37
+ if (!Atom.isSerializable(node.atom)) return
38
+ const atom = node.atom
39
+ const value = node.value()
40
+ const isInitial = Result.isResult(value) && Result.isInitial(value)
41
+ if (encodeInitialResultMode === "ignore" && isInitial) return
42
+ const encodedValue = atom[Atom.SerializableTypeId].encode(value)
43
+
44
+ // Create a promise that resolves when the atom moves out of Initial state
45
+ let resultPromise: Promise<unknown> | undefined
46
+ if (encodeInitialResultMode === "promise" && isInitial) {
47
+ resultPromise = new Promise((resolve) => {
48
+ const unsubscribe = registry.subscribe(atom, (newValue) => {
49
+ if (Result.isResult(newValue) && !Result.isInitial(newValue)) {
50
+ resolve(atom[Atom.SerializableTypeId].encode(newValue))
51
+ unsubscribe()
52
+ }
53
+ })
54
+ })
55
+ }
56
+
57
+ arr.push({
58
+ key: key as string,
59
+ value: encodedValue,
60
+ dehydratedAt: now,
61
+ resultPromise
62
+ })
63
+ })
64
+ return arr
65
+ }
66
+
67
+ /**
68
+ * @since 1.0.0
69
+ * @category hydration
70
+ */
71
+ export const hydrate = (
72
+ registry: Registry.Registry,
73
+ dehydratedState: Iterable<DehydratedAtom>
74
+ ): void => {
75
+ for (const datom of dehydratedState) {
76
+ registry.setSerializable(datom.key, datom.value)
77
+
78
+ // If there's a resultPromise, it means this was in Initial state when dehydrated
79
+ // and we should wait for it to resolve to a non-Initial state, then update the registry
80
+ if (!datom.resultPromise) continue
81
+ datom.resultPromise.then((resolvedValue) => {
82
+ // Try to update the existing node directly instead of using setSerializable
83
+ const nodes = (registry as any).getNodes()
84
+ const node = nodes.get(datom.key)
85
+ if (node) {
86
+ // Decode the resolved value using the node's atom serializable decoder
87
+ const atom = node.atom as any
88
+ if (atom[Atom.SerializableTypeId]) {
89
+ const decoded = atom[Atom.SerializableTypeId].decode(resolvedValue)
90
+ node.setValue(decoded)
91
+ }
92
+ } else {
93
+ // Fallback to setSerializable if node doesn't exist yet
94
+ registry.setSerializable(datom.key, resolvedValue)
95
+ }
96
+ })
97
+ }
98
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as Context from "effect/Context"
5
+ import * as Effect from "effect/Effect"
6
+ import * as FiberRef from "effect/FiberRef"
7
+ import { dual } from "effect/Function"
8
+ import * as Layer from "effect/Layer"
9
+ import * as Mailbox from "effect/Mailbox"
10
+ import { hasProperty } from "effect/Predicate"
11
+ import * as Scope from "effect/Scope"
12
+ import * as Stream from "effect/Stream"
13
+ import type * as Atom from "./Atom.js"
14
+ import type { Registry } from "./index.js"
15
+ import * as internal from "./internal/registry.js"
16
+ import * as Result from "./Result.js"
17
+
18
+ /**
19
+ * @since 1.0.0
20
+ * @category type ids
21
+ */
22
+ export const TypeId: TypeId = "~effect-atom/atom/Registry"
23
+
24
+ /**
25
+ * @since 1.0.0
26
+ * @category type ids
27
+ */
28
+ export type TypeId = "~effect-atom/atom/Registry"
29
+
30
+ /**
31
+ * @since 1.0.0
32
+ * @category guards
33
+ */
34
+ export const isRegistry = (u: unknown): u is Registry => hasProperty(u, TypeId)
35
+
36
+ /**
37
+ * @since 1.0.0
38
+ * @category models
39
+ */
40
+ export interface Registry {
41
+ readonly [TypeId]: TypeId
42
+ readonly getNodes: () => ReadonlyMap<Atom.Atom<any> | string, Node<any>>
43
+ readonly get: <A>(atom: Atom.Atom<A>) => A
44
+ readonly mount: <A>(atom: Atom.Atom<A>) => () => void
45
+ readonly refresh: <A>(atom: Atom.Atom<A>) => void
46
+ readonly set: <R, W>(atom: Atom.Writable<R, W>, value: W) => void
47
+ readonly setSerializable: (key: string, encoded: unknown) => void
48
+ readonly modify: <R, W, A>(atom: Atom.Writable<R, W>, f: (_: R) => [returnValue: A, nextValue: W]) => A
49
+ readonly update: <R, W>(atom: Atom.Writable<R, W>, f: (_: R) => W) => void
50
+ readonly subscribe: <A>(atom: Atom.Atom<A>, f: (_: A) => void, options?: {
51
+ readonly immediate?: boolean
52
+ }) => () => void
53
+ readonly reset: () => void
54
+ readonly dispose: () => void
55
+ }
56
+
57
+ /**
58
+ * @since 1.0.0
59
+ * @category models
60
+ */
61
+ interface Node<A> {
62
+ readonly atom: Atom.Atom<A>
63
+ readonly value: () => A
64
+ }
65
+
66
+ /**
67
+ * @since 1.0.0
68
+ * @category constructors
69
+ */
70
+ export const make: (
71
+ options?: {
72
+ readonly initialValues?: Iterable<readonly [Atom.Atom<any>, any]> | undefined
73
+ readonly scheduleTask?: ((f: () => void) => void) | undefined
74
+ readonly timeoutResolution?: number | undefined
75
+ readonly defaultIdleTTL?: number | undefined
76
+ } | undefined
77
+ ) => Registry = internal.make
78
+
79
+ /**
80
+ * @since 1.0.0
81
+ * @category Tags
82
+ */
83
+ export class AtomRegistry extends Context.Tag("@effect/atom/Registry/CurrentRegistry")<
84
+ AtomRegistry,
85
+ Registry
86
+ >() {}
87
+
88
+ /**
89
+ * @since 1.0.0
90
+ * @category Layers
91
+ */
92
+ export const layerOptions = (options?: {
93
+ readonly initialValues?: Iterable<readonly [Atom.Atom<any>, any]> | undefined
94
+ readonly scheduleTask?: ((f: () => void) => void) | undefined
95
+ readonly timeoutResolution?: number | undefined
96
+ readonly defaultIdleTTL?: number | undefined
97
+ }): Layer.Layer<AtomRegistry> =>
98
+ Layer.scoped(
99
+ AtomRegistry,
100
+ Effect.gen(function*() {
101
+ const scope = yield* Effect.scope
102
+ const scheduler = yield* FiberRef.get(FiberRef.currentScheduler)
103
+ const registry = internal.make({
104
+ ...options,
105
+ scheduleTask: options?.scheduleTask ?? ((f) => scheduler.scheduleTask(f, 0))
106
+ })
107
+ yield* Scope.addFinalizer(scope, Effect.sync(() => registry.dispose()))
108
+ return registry
109
+ })
110
+ )
111
+
112
+ /**
113
+ * @since 1.0.0
114
+ * @category Layers
115
+ */
116
+ export const layer: Layer.Layer<Registry.AtomRegistry> = layerOptions()
117
+
118
+ // -----------------------------------------------------------------------------
119
+ // conversions
120
+ // -----------------------------------------------------------------------------
121
+
122
+ /**
123
+ * @since 1.0.0
124
+ * @category Conversions
125
+ */
126
+ export const toStream: {
127
+ <A>(atom: Atom.Atom<A>): (self: Registry) => Stream.Stream<A>
128
+ <A>(self: Registry, atom: Atom.Atom<A>): Stream.Stream<A>
129
+ } = dual(
130
+ 2,
131
+ <A>(self: Registry, atom: Atom.Atom<A>) =>
132
+ Stream.unwrapScoped(
133
+ Effect.contextWithEffect((context: Context.Context<Scope.Scope>) => {
134
+ const scope = Context.get(context, Scope.Scope)
135
+ return Mailbox.make<A>().pipe(
136
+ Effect.tap((mailbox) => {
137
+ const cancel = self.subscribe(atom, (value) => mailbox.unsafeOffer(value), {
138
+ immediate: true
139
+ })
140
+ return Scope.addFinalizer(
141
+ scope,
142
+ Effect.suspend(() => {
143
+ cancel()
144
+ return mailbox.shutdown
145
+ })
146
+ )
147
+ }),
148
+ Effect.uninterruptible,
149
+ Effect.map((mailbox) => Mailbox.toStream(mailbox))
150
+ )
151
+ })
152
+ )
153
+ )
154
+
155
+ /**
156
+ * @since 1.0.0
157
+ * @category Conversions
158
+ */
159
+ export const toStreamResult: {
160
+ <A, E>(atom: Atom.Atom<Result.Result<A, E>>): (self: Registry) => Stream.Stream<A, E>
161
+ <A, E>(self: Registry, atom: Atom.Atom<Result.Result<A, E>>): Stream.Stream<A, E>
162
+ } = dual(
163
+ 2,
164
+ <A, E>(self: Registry, atom: Atom.Atom<Result.Result<A, E>>): Stream.Stream<A, E> =>
165
+ toStream(self, atom).pipe(
166
+ Stream.filter(Result.isNotInitial),
167
+ Stream.mapEffect((result) =>
168
+ result._tag === "Success" ? Effect.succeed(result.value) : Effect.failCause(result.cause)
169
+ )
170
+ )
171
+ )
172
+
173
+ /**
174
+ * @since 1.0.0
175
+ * @category Conversions
176
+ */
177
+ export const getResult: {
178
+ <A, E>(atom: Atom.Atom<Result.Result<A, E>>, options?: {
179
+ readonly suspendOnWaiting?: boolean | undefined
180
+ }): (self: Registry) => Effect.Effect<A, E>
181
+ <A, E>(self: Registry, atom: Atom.Atom<Result.Result<A, E>>, options?: {
182
+ readonly suspendOnWaiting?: boolean | undefined
183
+ }): Effect.Effect<A, E>
184
+ } = dual(
185
+ (args) => isRegistry(args[0]),
186
+ <A, E>(self: Registry, atom: Atom.Atom<Result.Result<A, E>>, options?: {
187
+ readonly suspendOnWaiting?: boolean | undefined
188
+ }): Effect.Effect<A, E> => {
189
+ const suspendOnWaiting = options?.suspendOnWaiting ?? false
190
+ return Effect.async((resume) => {
191
+ const result = self.get(atom)
192
+ if (result._tag !== "Initial" && !(suspendOnWaiting && result.waiting)) {
193
+ return resume(Result.toExit(result) as any)
194
+ }
195
+ const cancel = self.subscribe(atom, (value) => {
196
+ if (value._tag !== "Initial" && !(suspendOnWaiting && value.waiting)) {
197
+ resume(Result.toExit(value) as any)
198
+ cancel()
199
+ }
200
+ })
201
+ return Effect.sync(cancel)
202
+ })
203
+ }
204
+ )