@systemfsoftware/effect-atom 0.5.3

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.
@@ -0,0 +1,769 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { F as isResult, T as toExit, _ as isNotInitial, h as isInitial } from "./Result-DwtHFH70.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 };