@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.
- package/Atom/package.json +6 -0
- package/AtomRef/package.json +6 -0
- package/Hydration/package.json +6 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/Registry/package.json +6 -0
- package/Result/package.json +6 -0
- package/dist/cjs/Atom.js +1079 -0
- package/dist/cjs/Atom.js.map +1 -0
- package/dist/cjs/AtomRef.js +261 -0
- package/dist/cjs/AtomRef.js.map +1 -0
- package/dist/cjs/Hydration.js +100 -0
- package/dist/cjs/Hydration.js.map +1 -0
- package/dist/cjs/Registry.js +128 -0
- package/dist/cjs/Registry.js.map +1 -0
- package/dist/cjs/Result.js +454 -0
- package/dist/cjs/Result.js.map +1 -0
- package/dist/cjs/index.js +37 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internal/registry.js +701 -0
- package/dist/cjs/internal/registry.js.map +1 -0
- package/dist/cjs/internal/runtime.js +92 -0
- package/dist/cjs/internal/runtime.js.map +1 -0
- package/dist/dts/Atom.d.ts +597 -0
- package/dist/dts/Atom.d.ts.map +1 -0
- package/dist/dts/AtomRef.d.ts +55 -0
- package/dist/dts/AtomRef.d.ts.map +1 -0
- package/dist/dts/Hydration.d.ts +27 -0
- package/dist/dts/Hydration.d.ts.map +1 -0
- package/dist/dts/Registry.d.ts +115 -0
- package/dist/dts/Registry.d.ts.map +1 -0
- package/dist/dts/Result.d.ts +351 -0
- package/dist/dts/Result.d.ts.map +1 -0
- package/dist/dts/index.d.ts +21 -0
- package/dist/dts/index.d.ts.map +1 -0
- package/dist/dts/internal/registry.d.ts +2 -0
- package/dist/dts/internal/registry.d.ts.map +1 -0
- package/dist/dts/internal/runtime.d.ts +2 -0
- package/dist/dts/internal/runtime.d.ts.map +1 -0
- package/dist/esm/Atom.js +1029 -0
- package/dist/esm/Atom.js.map +1 -0
- package/dist/esm/AtomRef.js +232 -0
- package/dist/esm/AtomRef.js.map +1 -0
- package/dist/esm/Hydration.js +71 -0
- package/dist/esm/Hydration.js.map +1 -0
- package/dist/esm/Registry.js +98 -0
- package/dist/esm/Registry.js.map +1 -0
- package/dist/esm/Result.js +403 -0
- package/dist/esm/Result.js.map +1 -0
- package/dist/esm/index.js +21 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/registry.js +672 -0
- package/dist/esm/internal/registry.js.map +1 -0
- package/dist/esm/internal/runtime.js +64 -0
- package/dist/esm/internal/runtime.js.map +1 -0
- package/dist/esm/package.json +4 -0
- package/package.json +72 -0
- package/src/Atom.ts +1865 -0
- package/src/AtomRef.ts +282 -0
- package/src/Hydration.ts +98 -0
- package/src/Registry.ts +204 -0
- package/src/Result.ts +767 -0
- package/src/index.ts +24 -0
- package/src/internal/registry.ts +810 -0
- 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
|
+
}
|
package/src/Hydration.ts
ADDED
|
@@ -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
|
+
}
|
package/src/Registry.ts
ADDED
|
@@ -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
|
+
)
|