@systemfsoftware/effect-atom 0.5.3 → 1.0.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/CHANGELOG.md +101 -0
- package/package.json +4 -5
- package/dist/Atom-BhgqoTsf.mjs +0 -1348
- package/dist/Atom-CxKJ3i4d.d.ts +0 -1257
- package/dist/Atom-DGWqaX9a.d.ts +0 -1257
- package/dist/Atom-NsW01Gu1.mjs +0 -1348
- package/dist/Registry-B1ULaW2_.mjs +0 -769
- package/dist/Registry-DhP1TSer.mjs +0 -769
- package/dist/Result-B2vSp_sE.d.ts +0 -480
- package/dist/Result-CAjFdzam.d.ts +0 -490
- package/dist/Result-D8FNEzuX.mjs +0 -633
- package/dist/Result-DwtHFH70.mjs +0 -663
|
@@ -1,769 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { A as toExit, b as isNotInitial, v as isInitial, x as isResult } from "./Result-D8FNEzuX.mjs";
|
|
3
|
-
import * as Context from "effect/Context";
|
|
4
|
-
import * as Effect from "effect/Effect";
|
|
5
|
-
import * as Exit from "effect/Exit";
|
|
6
|
-
import * as Fiber from "effect/Fiber";
|
|
7
|
-
import { constVoid, dual } from "effect/Function";
|
|
8
|
-
import * as Layer from "effect/Layer";
|
|
9
|
-
import * as Option from "effect/Option";
|
|
10
|
-
import { MixedScheduler } from "effect/Scheduler";
|
|
11
|
-
import * as Scope$1 from "effect/Scope";
|
|
12
|
-
import * as Stream from "effect/Stream";
|
|
13
|
-
import { hasProperty } from "effect/Predicate";
|
|
14
|
-
import * as Queue from "effect/Queue";
|
|
15
|
-
import { Schema } from "effect";
|
|
16
|
-
Schema.Union([
|
|
17
|
-
Schema.TaggedStruct("Alive", {}),
|
|
18
|
-
Schema.TaggedStruct("RemoveNow", {}),
|
|
19
|
-
Schema.TaggedStruct("RemoveAfterTtl", { ttlMillis: Schema.Finite })
|
|
20
|
-
]);
|
|
21
|
-
const decideNodeFate = (input) => input.keepAlive || input.listenerCount > 0 || input.childCount > 0 || !input.isLive || input.isWaiting ? { _tag: "Alive" } : input.idleTTL !== 0 && (input.idleTTL !== void 0 || input.defaultIdleTTL !== void 0) ? {
|
|
22
|
-
_tag: "RemoveAfterTtl",
|
|
23
|
-
ttlMillis: input.idleTTL ?? input.defaultIdleTTL
|
|
24
|
-
} : { _tag: "RemoveNow" };
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/internal/node.ts
|
|
27
|
-
const notifyListener = (listener) => {
|
|
28
|
-
listener();
|
|
29
|
-
};
|
|
30
|
-
const NodeFlags = {
|
|
31
|
-
alive: 1,
|
|
32
|
-
initialized: 2,
|
|
33
|
-
waitingForValue: 4
|
|
34
|
-
};
|
|
35
|
-
const NodeState = {
|
|
36
|
-
uninitialized: NodeFlags.alive | NodeFlags.waitingForValue,
|
|
37
|
-
stale: NodeFlags.alive | NodeFlags.initialized | NodeFlags.waitingForValue,
|
|
38
|
-
valid: NodeFlags.alive | NodeFlags.initialized,
|
|
39
|
-
removed: 0
|
|
40
|
-
};
|
|
41
|
-
/** @internal */
|
|
42
|
-
var NodeImpl = class {
|
|
43
|
-
constructor(registry, atom) {
|
|
44
|
-
this.registry = registry;
|
|
45
|
-
this.atom = atom;
|
|
46
|
-
this.writeContext = new WriteContextImpl(registry, this);
|
|
47
|
-
}
|
|
48
|
-
registry;
|
|
49
|
-
atom;
|
|
50
|
-
state = NodeState.uninitialized;
|
|
51
|
-
lifetime;
|
|
52
|
-
writeContext;
|
|
53
|
-
preserveInitialValueOnBuild = false;
|
|
54
|
-
parents = /* @__PURE__ */ new Set();
|
|
55
|
-
previousParents;
|
|
56
|
-
children = /* @__PURE__ */ new Set();
|
|
57
|
-
listeners = /* @__PURE__ */ new Set();
|
|
58
|
-
skipInvalidation = false;
|
|
59
|
-
building = false;
|
|
60
|
-
invalidatedDuringBuild = false;
|
|
61
|
-
currentState() {
|
|
62
|
-
switch (this.state) {
|
|
63
|
-
case NodeState.uninitialized: return "uninitialized";
|
|
64
|
-
case NodeState.stale: return "stale";
|
|
65
|
-
case NodeState.valid: return "valid";
|
|
66
|
-
default: return "removed";
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
get canBeRemoved() {
|
|
70
|
-
const value = this._value;
|
|
71
|
-
return decideNodeFate({
|
|
72
|
-
keepAlive: this.atom.keepAlive,
|
|
73
|
-
listenerCount: this.listeners.size,
|
|
74
|
-
childCount: this.children.size,
|
|
75
|
-
isLive: this.state !== 0,
|
|
76
|
-
isWaiting: isResult(value) && isInitial(value) && value.waiting,
|
|
77
|
-
idleTTL: this.atom.idleTTL,
|
|
78
|
-
defaultIdleTTL: this.registry.defaultIdleTTL
|
|
79
|
-
})._tag !== "Alive";
|
|
80
|
-
}
|
|
81
|
-
_value;
|
|
82
|
-
value() {
|
|
83
|
-
if ((this.state & NodeFlags.waitingForValue) !== 0) {
|
|
84
|
-
this.lifetime = makeLifetime(this);
|
|
85
|
-
this.building = true;
|
|
86
|
-
const value = this.atom.read(this.lifetime);
|
|
87
|
-
this.building = false;
|
|
88
|
-
if ((this.state & NodeFlags.waitingForValue) !== 0) {
|
|
89
|
-
if (this.preserveInitialValueOnBuild) {
|
|
90
|
-
this.preserveInitialValueOnBuild = false;
|
|
91
|
-
this.state = NodeState.valid;
|
|
92
|
-
} else this.setValue(value);
|
|
93
|
-
}
|
|
94
|
-
if (this.previousParents) {
|
|
95
|
-
const parents = this.previousParents;
|
|
96
|
-
this.previousParents = void 0;
|
|
97
|
-
for (const parent of parents) {
|
|
98
|
-
parent.removeChild(this);
|
|
99
|
-
if (parent.canBeRemoved) this.registry.scheduleNodeRemoval(parent);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return this._value;
|
|
104
|
-
}
|
|
105
|
-
valueOption() {
|
|
106
|
-
if ((this.state & NodeFlags.initialized) === 0) return Option.none();
|
|
107
|
-
return Option.some(this._value);
|
|
108
|
-
}
|
|
109
|
-
setInitialValue(value) {
|
|
110
|
-
if ((this.state & NodeFlags.initialized) === 0) {
|
|
111
|
-
this.preserveInitialValueOnBuild = true;
|
|
112
|
-
this.state = NodeState.stale;
|
|
113
|
-
this._value = value;
|
|
114
|
-
if (batchState.phase === BatchPhase.collect) batchState.notify.add(this);
|
|
115
|
-
else this.notify();
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
this.setValue(value);
|
|
119
|
-
}
|
|
120
|
-
setValue(value) {
|
|
121
|
-
if ((this.state & NodeFlags.initialized) === 0) {
|
|
122
|
-
this.state = NodeState.valid;
|
|
123
|
-
this._value = value;
|
|
124
|
-
if (batchState.phase === BatchPhase.collect) batchState.notify.add(this);
|
|
125
|
-
else this.notify();
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
this.state = NodeState.valid;
|
|
129
|
-
if (this.atom.equals(this._value, value)) return;
|
|
130
|
-
this._value = value;
|
|
131
|
-
if (this.skipInvalidation) this.skipInvalidation = false;
|
|
132
|
-
else this.invalidateChildren();
|
|
133
|
-
if (this.listeners.size > 0) {
|
|
134
|
-
if (batchState.phase === BatchPhase.collect) batchState.notify.add(this);
|
|
135
|
-
else this.notify();
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
addParent(parent) {
|
|
139
|
-
this.parents.add(parent);
|
|
140
|
-
if (this.previousParents !== void 0) {
|
|
141
|
-
this.previousParents.delete(parent);
|
|
142
|
-
if (this.previousParents.size === 0) this.previousParents = void 0;
|
|
143
|
-
}
|
|
144
|
-
if (!parent.children.has(this)) {
|
|
145
|
-
parent.children.add(this);
|
|
146
|
-
if (parent.skipInvalidation) parent.skipInvalidation = false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
removeChild(child) {
|
|
150
|
-
this.children.delete(child);
|
|
151
|
-
}
|
|
152
|
-
invalidate() {
|
|
153
|
-
if (this.building && batchState.phase === BatchPhase.collect) this.invalidatedDuringBuild = true;
|
|
154
|
-
if (this.state === NodeState.valid) {
|
|
155
|
-
this.state = NodeState.stale;
|
|
156
|
-
this.disposeLifetime();
|
|
157
|
-
}
|
|
158
|
-
if (batchState.phase === BatchPhase.collect) batchState.stale.push(this);
|
|
159
|
-
else if (this.atom.lazy && this.listeners.size === 0 && !childrenAreActive(this.children)) {
|
|
160
|
-
this.invalidateChildren();
|
|
161
|
-
this.skipInvalidation = true;
|
|
162
|
-
} else this.value();
|
|
163
|
-
}
|
|
164
|
-
invalidateChildren() {
|
|
165
|
-
if (this.children.size === 0) return;
|
|
166
|
-
const children = this.children;
|
|
167
|
-
this.children = /* @__PURE__ */ new Set();
|
|
168
|
-
for (const child of children) child.invalidate();
|
|
169
|
-
}
|
|
170
|
-
notify() {
|
|
171
|
-
this.listeners.forEach(notifyListener);
|
|
172
|
-
if (batchState.phase === BatchPhase.commit) batchState.notify.delete(this);
|
|
173
|
-
}
|
|
174
|
-
disposeLifetime() {
|
|
175
|
-
if (this.lifetime !== void 0) {
|
|
176
|
-
this.lifetime.dispose();
|
|
177
|
-
this.lifetime = void 0;
|
|
178
|
-
}
|
|
179
|
-
if (this.parents.size !== 0) {
|
|
180
|
-
this.previousParents = this.parents;
|
|
181
|
-
this.parents = /* @__PURE__ */ new Set();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
remove() {
|
|
185
|
-
this.state = NodeState.removed;
|
|
186
|
-
this.listeners.clear();
|
|
187
|
-
if (this.lifetime === void 0) return;
|
|
188
|
-
this.disposeLifetime();
|
|
189
|
-
if (this.previousParents === void 0) return;
|
|
190
|
-
const parents = this.previousParents;
|
|
191
|
-
this.previousParents = void 0;
|
|
192
|
-
for (const parent of parents) {
|
|
193
|
-
parent.removeChild(this);
|
|
194
|
-
if (parent.canBeRemoved) this.registry.removeNode(parent);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
subscribe(listener) {
|
|
198
|
-
this.listeners.add(listener);
|
|
199
|
-
return () => this.listeners.delete(listener);
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
function childrenAreActive(children) {
|
|
203
|
-
if (children.size === 0) return false;
|
|
204
|
-
let current = children;
|
|
205
|
-
let stack;
|
|
206
|
-
let stackIndex = 0;
|
|
207
|
-
while (current !== void 0) {
|
|
208
|
-
for (const child of current) if (!child.atom.lazy || child.listeners.size > 0) return true;
|
|
209
|
-
else if (child.children.size > 0) {
|
|
210
|
-
if (stack === void 0) stack = [child.children];
|
|
211
|
-
else stack.push(child.children);
|
|
212
|
-
}
|
|
213
|
-
current = stack?.[stackIndex++];
|
|
214
|
-
}
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
const LifetimeProto = {
|
|
218
|
-
get registry() {
|
|
219
|
-
return this.node.registry;
|
|
220
|
-
},
|
|
221
|
-
addFinalizer(f) {
|
|
222
|
-
if (this.disposed) return f();
|
|
223
|
-
this.finalizers ??= [];
|
|
224
|
-
this.finalizers.push(f);
|
|
225
|
-
},
|
|
226
|
-
get(atom) {
|
|
227
|
-
if (this.disposed) return this.node.registry.get(atom);
|
|
228
|
-
const parent = this.node.registry.ensureNode(atom);
|
|
229
|
-
const value = parent.value();
|
|
230
|
-
this.node.addParent(parent);
|
|
231
|
-
return value;
|
|
232
|
-
},
|
|
233
|
-
result(atom, options) {
|
|
234
|
-
if (this.disposed || this.isFn) return this.resultOnce(atom, options);
|
|
235
|
-
const result = this.get(atom);
|
|
236
|
-
if (options?.suspendOnWaiting && result.waiting) return Effect.never;
|
|
237
|
-
switch (result._tag) {
|
|
238
|
-
case "Initial": return Effect.never;
|
|
239
|
-
case "Failure": return Exit.failCause(result.cause);
|
|
240
|
-
case "Success": return Effect.succeed(result.value);
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
resultOnce(atom, options) {
|
|
244
|
-
return Effect.callback((resume) => {
|
|
245
|
-
const result = this.once(atom);
|
|
246
|
-
if (result._tag !== "Initial" && !(options?.suspendOnWaiting && result.waiting)) return resume(toExit(result));
|
|
247
|
-
const cancel = this.node.registry.subscribe(atom, (result) => {
|
|
248
|
-
if (result._tag === "Initial" || options?.suspendOnWaiting && result.waiting) return;
|
|
249
|
-
cancel();
|
|
250
|
-
resume(toExit(result));
|
|
251
|
-
}, { immediate: false });
|
|
252
|
-
return Effect.sync(cancel);
|
|
253
|
-
});
|
|
254
|
-
},
|
|
255
|
-
setResult(atom, value) {
|
|
256
|
-
if (this.disposed) return Effect.never;
|
|
257
|
-
this.node.registry.set(atom, value);
|
|
258
|
-
return this.resultOnce(atom, { suspendOnWaiting: true });
|
|
259
|
-
},
|
|
260
|
-
some(atom) {
|
|
261
|
-
if (this.disposed || this.isFn) return this.someOnce(atom);
|
|
262
|
-
const result = this.get(atom);
|
|
263
|
-
return result._tag === "None" ? Effect.never : Effect.succeed(result.value);
|
|
264
|
-
},
|
|
265
|
-
someOnce(atom) {
|
|
266
|
-
return Effect.callback((resume) => {
|
|
267
|
-
const result = this.once(atom);
|
|
268
|
-
if (Option.isSome(result)) return resume(Effect.succeed(result.value));
|
|
269
|
-
const cancel = this.node.registry.subscribe(atom, (result) => {
|
|
270
|
-
if (Option.isNone(result)) return;
|
|
271
|
-
cancel();
|
|
272
|
-
resume(Effect.succeed(result.value));
|
|
273
|
-
}, { immediate: false });
|
|
274
|
-
return Effect.sync(cancel);
|
|
275
|
-
});
|
|
276
|
-
},
|
|
277
|
-
once(atom) {
|
|
278
|
-
return this.node.registry.get(atom);
|
|
279
|
-
},
|
|
280
|
-
self() {
|
|
281
|
-
if (this.disposed) return Option.none();
|
|
282
|
-
return this.node.valueOption();
|
|
283
|
-
},
|
|
284
|
-
refresh(atom) {
|
|
285
|
-
if (this.disposed) return;
|
|
286
|
-
this.node.registry.refresh(atom);
|
|
287
|
-
},
|
|
288
|
-
refreshSelf() {
|
|
289
|
-
if (this.disposed) return;
|
|
290
|
-
this.node.invalidate();
|
|
291
|
-
},
|
|
292
|
-
mount(atom) {
|
|
293
|
-
if (this.disposed) return;
|
|
294
|
-
this.addFinalizer(this.node.registry.mount(atom));
|
|
295
|
-
},
|
|
296
|
-
subscribe(atom, f, options) {
|
|
297
|
-
if (this.disposed) return;
|
|
298
|
-
this.addFinalizer(this.node.registry.subscribe(atom, f, options));
|
|
299
|
-
},
|
|
300
|
-
setSelf(a) {
|
|
301
|
-
if (this.disposed) return;
|
|
302
|
-
this.node.setValue(a);
|
|
303
|
-
},
|
|
304
|
-
set(atom, value) {
|
|
305
|
-
if (this.disposed) return;
|
|
306
|
-
this.node.registry.set(atom, value);
|
|
307
|
-
},
|
|
308
|
-
stream(atom, options) {
|
|
309
|
-
if (this.disposed) return Stream.empty;
|
|
310
|
-
return Stream.callback((queue) => Effect.sync(() => {
|
|
311
|
-
this.subscribe(atom, (value) => Queue.offerUnsafe(queue, value), { immediate: !options?.withoutInitialValue });
|
|
312
|
-
}));
|
|
313
|
-
},
|
|
314
|
-
streamResult(atom, options) {
|
|
315
|
-
return this.stream(atom, options).pipe(Stream.filter(isNotInitial), Stream.mapEffect((result) => result._tag === "Success" ? Effect.succeed(result.value) : Effect.failCause(result.cause)));
|
|
316
|
-
},
|
|
317
|
-
dispose() {
|
|
318
|
-
this.disposed = true;
|
|
319
|
-
if (this.finalizers === void 0) return;
|
|
320
|
-
const finalizers = this.finalizers;
|
|
321
|
-
this.finalizers = void 0;
|
|
322
|
-
for (let i = finalizers.length - 1; i >= 0; i--) finalizers[i]();
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
const makeLifetime = (node) => {
|
|
326
|
-
function get(atom) {
|
|
327
|
-
if (get.disposed) return node.registry.get(atom);
|
|
328
|
-
else if (get.isFn) return node.registry.get(atom);
|
|
329
|
-
const parent = node.registry.ensureNode(atom);
|
|
330
|
-
const value = parent.value();
|
|
331
|
-
node.addParent(parent);
|
|
332
|
-
return value;
|
|
333
|
-
}
|
|
334
|
-
Object.setPrototypeOf(get, LifetimeProto);
|
|
335
|
-
get.isFn = false;
|
|
336
|
-
get.disposed = false;
|
|
337
|
-
get.finalizers = void 0;
|
|
338
|
-
get.node = node;
|
|
339
|
-
return get;
|
|
340
|
-
};
|
|
341
|
-
var WriteContextImpl = class {
|
|
342
|
-
constructor(registry, node) {
|
|
343
|
-
this.registry = registry;
|
|
344
|
-
this.node = node;
|
|
345
|
-
}
|
|
346
|
-
registry;
|
|
347
|
-
node;
|
|
348
|
-
get(atom) {
|
|
349
|
-
return this.registry.get(atom);
|
|
350
|
-
}
|
|
351
|
-
set(atom, value) {
|
|
352
|
-
return this.registry.set(atom, value);
|
|
353
|
-
}
|
|
354
|
-
setSelf(value) {
|
|
355
|
-
return this.node.setValue(value);
|
|
356
|
-
}
|
|
357
|
-
refreshSelf() {
|
|
358
|
-
return this.node.invalidate();
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
/** @internal */
|
|
362
|
-
const BatchPhase = {
|
|
363
|
-
disabled: 0,
|
|
364
|
-
collect: 1,
|
|
365
|
-
commit: 2
|
|
366
|
-
};
|
|
367
|
-
/** @internal */
|
|
368
|
-
const batchState = {
|
|
369
|
-
phase: BatchPhase.disabled,
|
|
370
|
-
depth: 0,
|
|
371
|
-
stale: [],
|
|
372
|
-
notify: /* @__PURE__ */ new Set()
|
|
373
|
-
};
|
|
374
|
-
/** @internal */
|
|
375
|
-
function batch(f) {
|
|
376
|
-
batchState.phase = BatchPhase.collect;
|
|
377
|
-
batchState.depth++;
|
|
378
|
-
try {
|
|
379
|
-
f();
|
|
380
|
-
if (batchState.depth === 1) {
|
|
381
|
-
for (let i = 0; i < batchState.stale.length; i++) batchRebuildNode(batchState.stale[i]);
|
|
382
|
-
batchState.phase = BatchPhase.commit;
|
|
383
|
-
for (const node of batchState.notify) node.notify();
|
|
384
|
-
batchState.notify.clear();
|
|
385
|
-
}
|
|
386
|
-
} finally {
|
|
387
|
-
batchState.depth--;
|
|
388
|
-
if (batchState.depth === 0) {
|
|
389
|
-
batchState.phase = BatchPhase.disabled;
|
|
390
|
-
batchState.stale = [];
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
function batchRebuildNode(node) {
|
|
395
|
-
if (node.state === NodeState.valid) {
|
|
396
|
-
if (!node.invalidatedDuringBuild) return;
|
|
397
|
-
node.invalidatedDuringBuild = false;
|
|
398
|
-
node.state = NodeState.stale;
|
|
399
|
-
node.disposeLifetime();
|
|
400
|
-
}
|
|
401
|
-
for (const parent of node.parents) if (parent.state !== NodeState.valid) batchRebuildNode(parent);
|
|
402
|
-
if (node.state !== NodeState.valid) node.value();
|
|
403
|
-
}
|
|
404
|
-
//#endregion
|
|
405
|
-
//#region src/Registry.ts
|
|
406
|
-
/**
|
|
407
|
-
* Stores and runs atoms for one reactive runtime.
|
|
408
|
-
*
|
|
409
|
-
* An `AtomRegistry` evaluates atoms, caches their current values, tracks
|
|
410
|
-
* dependencies, applies writes and refreshes, manages subscriptions, and
|
|
411
|
-
* disposes unused nodes. Each registry is independent, so the same atom can hold
|
|
412
|
-
* different values in different registries. Serializable atom values can also be
|
|
413
|
-
* preloaded before the first read.
|
|
414
|
-
*
|
|
415
|
-
* @since 4.0.0
|
|
416
|
-
*/
|
|
417
|
-
var Registry_exports = /* @__PURE__ */ __exportAll({
|
|
418
|
-
AtomRegistry: () => AtomRegistry,
|
|
419
|
-
BatchPhase: () => BatchPhase,
|
|
420
|
-
RegistryImpl: () => RegistryImpl,
|
|
421
|
-
TypeId: () => TypeId,
|
|
422
|
-
batch: () => batch,
|
|
423
|
-
batchState: () => batchState,
|
|
424
|
-
getResult: () => getResult,
|
|
425
|
-
isAtomRegistry: () => isAtomRegistry,
|
|
426
|
-
layer: () => layer,
|
|
427
|
-
layerOptions: () => layerOptions,
|
|
428
|
-
make: () => make,
|
|
429
|
-
mount: () => mount,
|
|
430
|
-
toStream: () => toStream,
|
|
431
|
-
toStreamResult: () => toStreamResult
|
|
432
|
-
});
|
|
433
|
-
/**
|
|
434
|
-
* The runtime type id used to identify `AtomRegistry` services and values.
|
|
435
|
-
*
|
|
436
|
-
* @category type IDs
|
|
437
|
-
* @since 4.0.0
|
|
438
|
-
*/
|
|
439
|
-
const TypeId = "~effect-atom/atom/Registry";
|
|
440
|
-
/**
|
|
441
|
-
* Returns `true` when the value has the `AtomRegistry` type id.
|
|
442
|
-
*
|
|
443
|
-
* @category guards
|
|
444
|
-
* @since 4.0.0
|
|
445
|
-
*/
|
|
446
|
-
const isAtomRegistry = (u) => hasProperty(u, TypeId);
|
|
447
|
-
/**
|
|
448
|
-
* Creates an `AtomRegistry`.
|
|
449
|
-
*
|
|
450
|
-
* **Details**
|
|
451
|
-
*
|
|
452
|
-
* Options can preload initial atom values, provide a custom task scheduler,
|
|
453
|
-
* configure timeout bucket resolution, and set a default idle time-to-live for
|
|
454
|
-
* unused atoms.
|
|
455
|
-
*
|
|
456
|
-
* @category constructors
|
|
457
|
-
* @since 4.0.0
|
|
458
|
-
*/
|
|
459
|
-
const make = (options) => new RegistryImpl(options?.initialValues, options?.scheduleTask, options?.timeoutResolution, options?.defaultIdleTTL);
|
|
460
|
-
/**
|
|
461
|
-
* Service tag for the active atom runtime cache.
|
|
462
|
-
*
|
|
463
|
-
* **When to use**
|
|
464
|
-
*
|
|
465
|
-
* Use to access or provide the registry that stores atom values,
|
|
466
|
-
* dependencies, subscriptions, and disposal state for a reactive lifetime.
|
|
467
|
-
*
|
|
468
|
-
* @category services
|
|
469
|
-
* @since 4.0.0
|
|
470
|
-
*/
|
|
471
|
-
var AtomRegistry = class extends Context.Service()(TypeId) {};
|
|
472
|
-
/**
|
|
473
|
-
* Creates a layer that provides an `AtomRegistry` configured with the supplied
|
|
474
|
-
* options.
|
|
475
|
-
*
|
|
476
|
-
* **Details**
|
|
477
|
-
*
|
|
478
|
-
* The registry is disposed when the layer scope is finalized.
|
|
479
|
-
*
|
|
480
|
-
* @category layers
|
|
481
|
-
* @since 4.0.0
|
|
482
|
-
*/
|
|
483
|
-
const layerOptions = (options) => Layer.effect(AtomRegistry, Effect.gen(function* () {
|
|
484
|
-
const scope = yield* Effect.scope;
|
|
485
|
-
const registry = make(options);
|
|
486
|
-
yield* Scope$1.addFinalizer(scope, Effect.sync(() => registry.dispose()));
|
|
487
|
-
return registry;
|
|
488
|
-
}));
|
|
489
|
-
/**
|
|
490
|
-
* The default layer that provides a fresh `AtomRegistry`.
|
|
491
|
-
*
|
|
492
|
-
* @category layers
|
|
493
|
-
* @since 4.0.0
|
|
494
|
-
*/
|
|
495
|
-
const layer = layerOptions();
|
|
496
|
-
/**
|
|
497
|
-
* Converts an atom in this registry into a stream.
|
|
498
|
-
*
|
|
499
|
-
* **Details**
|
|
500
|
-
*
|
|
501
|
-
* The stream emits the current value immediately, emits subsequent changes, and
|
|
502
|
-
* unsubscribes from the registry when the stream scope closes.
|
|
503
|
-
*
|
|
504
|
-
* @category converting
|
|
505
|
-
* @since 4.0.0
|
|
506
|
-
*/
|
|
507
|
-
const toStream = dual(2, (self, atom) => Stream.callback((queue) => Effect.suspend(() => {
|
|
508
|
-
const fiber = Fiber.getCurrent();
|
|
509
|
-
const scope = Context.getUnsafe(fiber.context, Scope$1.Scope);
|
|
510
|
-
const cancel = self.subscribe(atom, (value) => Queue.offerUnsafe(queue, value), { immediate: true });
|
|
511
|
-
return Scope$1.addFinalizer(scope, Effect.sync(cancel));
|
|
512
|
-
})));
|
|
513
|
-
/**
|
|
514
|
-
* Converts an `AsyncResult` atom in this registry into a stream of successful
|
|
515
|
-
* values.
|
|
516
|
-
*
|
|
517
|
-
* **Details**
|
|
518
|
-
*
|
|
519
|
-
* Initial results are skipped, failures fail the stream with their cause, and
|
|
520
|
-
* duplicate stream values are dropped with `Stream.changes`.
|
|
521
|
-
*
|
|
522
|
-
* @category converting
|
|
523
|
-
* @since 4.0.0
|
|
524
|
-
*/
|
|
525
|
-
const toStreamResult = dual(2, (self, atom) => toStream(self, atom).pipe(Stream.filter(isNotInitial), Stream.mapEffect((result) => result._tag === "Success" ? Effect.succeed(result.value) : Effect.failCause(result.cause)), Stream.changes));
|
|
526
|
-
/**
|
|
527
|
-
* Reads an `AsyncResult` atom from this registry as an effect.
|
|
528
|
-
*
|
|
529
|
-
* **Details**
|
|
530
|
-
*
|
|
531
|
-
* The effect waits for the result to leave `Initial`, and also waits through
|
|
532
|
-
* waiting results when `suspendOnWaiting` is enabled.
|
|
533
|
-
*
|
|
534
|
-
* @category converting
|
|
535
|
-
* @since 4.0.0
|
|
536
|
-
*/
|
|
537
|
-
const getResult = dual((args) => isAtomRegistry(args[0]), (self, atom, options) => {
|
|
538
|
-
const suspendOnWaiting = options?.suspendOnWaiting ?? false;
|
|
539
|
-
return Effect.callback((resume) => {
|
|
540
|
-
const result = self.get(atom);
|
|
541
|
-
if (result._tag !== "Initial" && !(suspendOnWaiting && result.waiting)) return resume(toExit(result));
|
|
542
|
-
const cancel = self.subscribe(atom, (value) => {
|
|
543
|
-
if (value._tag !== "Initial" && !(suspendOnWaiting && value.waiting)) {
|
|
544
|
-
resume(toExit(value));
|
|
545
|
-
cancel();
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
return Effect.sync(cancel);
|
|
549
|
-
});
|
|
550
|
-
});
|
|
551
|
-
/**
|
|
552
|
-
* Mounts an atom in this registry for the lifetime of the current scope.
|
|
553
|
-
*
|
|
554
|
-
* **Details**
|
|
555
|
-
*
|
|
556
|
-
* The atom is subscribed with a no-op listener and the subscription is released
|
|
557
|
-
* when the scope finalizer runs.
|
|
558
|
-
*
|
|
559
|
-
* @category converting
|
|
560
|
-
* @since 4.0.0
|
|
561
|
-
*/
|
|
562
|
-
const mount = dual(2, (self, atom) => Effect.acquireRelease(Effect.sync(() => self.mount(atom)), (release) => Effect.sync(release)));
|
|
563
|
-
const constImmediate = { immediate: true };
|
|
564
|
-
const SerializableTypeId = "~effect-atom/atom/Atom/Serializable";
|
|
565
|
-
const atomKey = (atom) => SerializableTypeId in atom ? atom[SerializableTypeId].key : atom;
|
|
566
|
-
/**
|
|
567
|
-
* @internal
|
|
568
|
-
*/
|
|
569
|
-
var RegistryImpl = class {
|
|
570
|
-
[TypeId];
|
|
571
|
-
timeoutResolution;
|
|
572
|
-
defaultIdleTTL;
|
|
573
|
-
scheduler;
|
|
574
|
-
schedulerAsync;
|
|
575
|
-
dispatcher;
|
|
576
|
-
onNodeAdded;
|
|
577
|
-
onNodeRemoved;
|
|
578
|
-
constructor(initialValues, scheduleTask, timeoutResolution, defaultIdleTTL) {
|
|
579
|
-
this[TypeId] = TypeId;
|
|
580
|
-
this.scheduler = new MixedScheduler("sync", scheduleTask);
|
|
581
|
-
this.schedulerAsync = new MixedScheduler("async", scheduleTask);
|
|
582
|
-
this.dispatcher = this.schedulerAsync.makeDispatcher();
|
|
583
|
-
this.defaultIdleTTL = defaultIdleTTL;
|
|
584
|
-
if (timeoutResolution === void 0 && defaultIdleTTL !== void 0) this.timeoutResolution = Math.round(defaultIdleTTL / 2);
|
|
585
|
-
else this.timeoutResolution = timeoutResolution ?? 1e3;
|
|
586
|
-
if (initialValues !== void 0) for (const [atom, value] of initialValues) this.setInitialValue(atom, value);
|
|
587
|
-
}
|
|
588
|
-
setInitialValue(atom, value) {
|
|
589
|
-
let target = atom;
|
|
590
|
-
while (target.initialValueTarget) target = target.initialValueTarget;
|
|
591
|
-
this.ensureNode(target).setInitialValue(value);
|
|
592
|
-
}
|
|
593
|
-
nodes = /* @__PURE__ */ new Map();
|
|
594
|
-
preloadedSerializable = /* @__PURE__ */ new Map();
|
|
595
|
-
timeoutBuckets = /* @__PURE__ */ new Map();
|
|
596
|
-
nodeTimeoutBucket = /* @__PURE__ */ new Map();
|
|
597
|
-
disposed = false;
|
|
598
|
-
getNodes() {
|
|
599
|
-
return this.nodes;
|
|
600
|
-
}
|
|
601
|
-
get(atom) {
|
|
602
|
-
return this.ensureNode(atom).value();
|
|
603
|
-
}
|
|
604
|
-
set(atom, value) {
|
|
605
|
-
atom.write(this.ensureNode(atom).writeContext, value);
|
|
606
|
-
}
|
|
607
|
-
setSerializable(key, encoded) {
|
|
608
|
-
const node = this.nodes.get(key);
|
|
609
|
-
if (node === void 0) {
|
|
610
|
-
this.preloadedSerializable.set(key, encoded);
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
this.applySerializableValue(node, encoded);
|
|
614
|
-
}
|
|
615
|
-
applySerializableValue(node, encoded) {
|
|
616
|
-
const atom = node.atom;
|
|
617
|
-
if (!(SerializableTypeId in atom)) return;
|
|
618
|
-
let decoded;
|
|
619
|
-
try {
|
|
620
|
-
decoded = atom[SerializableTypeId].decode(encoded);
|
|
621
|
-
} catch {
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
let target = atom;
|
|
625
|
-
while (target.initialValueTarget) target = target.initialValueTarget;
|
|
626
|
-
if (target === atom) node.setValue(decoded);
|
|
627
|
-
else this.ensureNode(target).setInitialValue(decoded);
|
|
628
|
-
}
|
|
629
|
-
modify(atom, f) {
|
|
630
|
-
const node = this.ensureNode(atom);
|
|
631
|
-
const result = f(node.value());
|
|
632
|
-
atom.write(node.writeContext, result[1]);
|
|
633
|
-
return result[0];
|
|
634
|
-
}
|
|
635
|
-
update(atom, f) {
|
|
636
|
-
const node = this.ensureNode(atom);
|
|
637
|
-
atom.write(node.writeContext, f(node.value()));
|
|
638
|
-
}
|
|
639
|
-
refresh = (atom) => {
|
|
640
|
-
if (atom.refresh !== void 0) atom.refresh(this.refresh);
|
|
641
|
-
else this.invalidateAtom(atom);
|
|
642
|
-
};
|
|
643
|
-
subscribe(atom, f, options) {
|
|
644
|
-
const node = this.ensureNode(atom);
|
|
645
|
-
if (options?.immediate) f(node.value());
|
|
646
|
-
const remove = node.subscribe(function() {
|
|
647
|
-
f(node._value);
|
|
648
|
-
});
|
|
649
|
-
return () => {
|
|
650
|
-
remove();
|
|
651
|
-
if (node.canBeRemoved) this.scheduleNodeRemoval(node);
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
mount(atom) {
|
|
655
|
-
return this.subscribe(atom, constVoid, constImmediate);
|
|
656
|
-
}
|
|
657
|
-
atomHasTtl(atom) {
|
|
658
|
-
return !atom.keepAlive && atom.idleTTL !== 0 && (atom.idleTTL !== void 0 || this.defaultIdleTTL !== void 0);
|
|
659
|
-
}
|
|
660
|
-
ensureNode(atom) {
|
|
661
|
-
const key = atomKey(atom);
|
|
662
|
-
let node = this.nodes.get(key);
|
|
663
|
-
if (node === void 0) {
|
|
664
|
-
node = this.createNode(atom);
|
|
665
|
-
this.nodes.set(key, node);
|
|
666
|
-
this.onNodeAdded?.(node);
|
|
667
|
-
} else if (this.atomHasTtl(atom)) this.removeNodeTimeout(node);
|
|
668
|
-
if (typeof key === "string" && this.preloadedSerializable.has(key)) {
|
|
669
|
-
const encoded = this.preloadedSerializable.get(key);
|
|
670
|
-
this.preloadedSerializable.delete(key);
|
|
671
|
-
this.applySerializableValue(node, encoded);
|
|
672
|
-
}
|
|
673
|
-
return node;
|
|
674
|
-
}
|
|
675
|
-
createNode(atom) {
|
|
676
|
-
if (this.disposed) throw new Error(`Cannot access Atom ${atom.label?.[0] ?? "unknown"}: registry is disposed`);
|
|
677
|
-
if (!atom.keepAlive) this.scheduleAtomRemoval(atom);
|
|
678
|
-
return new NodeImpl(this, atom);
|
|
679
|
-
}
|
|
680
|
-
invalidateAtom = (atom) => {
|
|
681
|
-
this.ensureNode(atom).invalidate();
|
|
682
|
-
};
|
|
683
|
-
scheduleAtomRemoval(atom) {
|
|
684
|
-
this.dispatcher.scheduleTask(() => {
|
|
685
|
-
const node = this.nodes.get(atomKey(atom));
|
|
686
|
-
if (node !== void 0 && node.canBeRemoved) this.removeNode(node);
|
|
687
|
-
}, 0);
|
|
688
|
-
}
|
|
689
|
-
scheduleNodeRemoval(node) {
|
|
690
|
-
this.dispatcher.scheduleTask(() => {
|
|
691
|
-
if (node.canBeRemoved) this.removeNode(node);
|
|
692
|
-
}, 0);
|
|
693
|
-
}
|
|
694
|
-
removeNode(node) {
|
|
695
|
-
if (this.atomHasTtl(node.atom)) this.setNodeTimeout(node);
|
|
696
|
-
else {
|
|
697
|
-
this.nodes.delete(atomKey(node.atom));
|
|
698
|
-
node.remove();
|
|
699
|
-
this.onNodeRemoved?.(node);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
setNodeTimeout(node) {
|
|
703
|
-
if (this.nodeTimeoutBucket.has(node)) return;
|
|
704
|
-
let idleTTL = node.atom.idleTTL ?? this.defaultIdleTTL;
|
|
705
|
-
if (this.#currentSweepTTL !== null) {
|
|
706
|
-
idleTTL -= this.#currentSweepTTL;
|
|
707
|
-
if (idleTTL <= 0) {
|
|
708
|
-
if (node.canBeRemoved) {
|
|
709
|
-
this.nodes.delete(atomKey(node.atom));
|
|
710
|
-
node.remove();
|
|
711
|
-
this.onNodeRemoved?.(node);
|
|
712
|
-
}
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
const ttl = Math.ceil(idleTTL / this.timeoutResolution) * this.timeoutResolution;
|
|
717
|
-
const timestamp = Date.now() + ttl;
|
|
718
|
-
const bucket = timestamp - timestamp % this.timeoutResolution + this.timeoutResolution;
|
|
719
|
-
let entry = this.timeoutBuckets.get(bucket);
|
|
720
|
-
if (entry === void 0) {
|
|
721
|
-
entry = [/* @__PURE__ */ new Set(), setTimeout(() => this.sweepBucket(bucket), bucket - Date.now())];
|
|
722
|
-
this.timeoutBuckets.set(bucket, entry);
|
|
723
|
-
}
|
|
724
|
-
entry[0].add(node);
|
|
725
|
-
this.nodeTimeoutBucket.set(node, bucket);
|
|
726
|
-
}
|
|
727
|
-
removeNodeTimeout(node) {
|
|
728
|
-
const bucket = this.nodeTimeoutBucket.get(node);
|
|
729
|
-
if (bucket === void 0) return;
|
|
730
|
-
this.nodeTimeoutBucket.delete(node);
|
|
731
|
-
this.scheduleNodeRemoval(node);
|
|
732
|
-
const [nodes, handle] = this.timeoutBuckets.get(bucket);
|
|
733
|
-
nodes.delete(node);
|
|
734
|
-
if (nodes.size === 0) {
|
|
735
|
-
clearTimeout(handle);
|
|
736
|
-
this.timeoutBuckets.delete(bucket);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
#currentSweepTTL = null;
|
|
740
|
-
sweepBucket(bucket) {
|
|
741
|
-
const nodes = this.timeoutBuckets.get(bucket)[0];
|
|
742
|
-
this.timeoutBuckets.delete(bucket);
|
|
743
|
-
nodes.forEach((node) => {
|
|
744
|
-
this.nodeTimeoutBucket.delete(node);
|
|
745
|
-
if (!node.canBeRemoved) return;
|
|
746
|
-
this.nodes.delete(atomKey(node.atom));
|
|
747
|
-
this.onNodeRemoved?.(node);
|
|
748
|
-
this.#currentSweepTTL = node.atom.idleTTL ?? this.defaultIdleTTL;
|
|
749
|
-
node.remove();
|
|
750
|
-
this.#currentSweepTTL = null;
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
reset() {
|
|
754
|
-
this.timeoutBuckets.forEach(([, handle]) => clearTimeout(handle));
|
|
755
|
-
this.timeoutBuckets.clear();
|
|
756
|
-
this.nodeTimeoutBucket.clear();
|
|
757
|
-
this.nodes.forEach((node) => {
|
|
758
|
-
node.remove();
|
|
759
|
-
this.onNodeRemoved?.(node);
|
|
760
|
-
});
|
|
761
|
-
this.nodes.clear();
|
|
762
|
-
}
|
|
763
|
-
dispose() {
|
|
764
|
-
this.disposed = true;
|
|
765
|
-
this.reset();
|
|
766
|
-
}
|
|
767
|
-
};
|
|
768
|
-
//#endregion
|
|
769
|
-
export { getResult as a, layerOptions as c, toStream as d, toStreamResult as f, batchState as h, TypeId as i, make as l, batch as m, RegistryImpl as n, isAtomRegistry as o, BatchPhase as p, Registry_exports as r, layer as s, AtomRegistry as t, mount as u };
|