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