jotai-state-tree 1.4.4 → 1.5.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.
@@ -0,0 +1,3984 @@
1
+ // src/primitives.ts
2
+ function createSimpleType(name, validator, defaultValue) {
3
+ return {
4
+ name,
5
+ _kind: "simple",
6
+ _C: void 0,
7
+ _S: void 0,
8
+ _T: void 0,
9
+ create(snapshot) {
10
+ if (snapshot === void 0) {
11
+ if (defaultValue !== void 0) {
12
+ return defaultValue;
13
+ }
14
+ throw new Error(`[jotai-state-tree] A value of type '${name}' is required`);
15
+ }
16
+ if (!validator(snapshot)) {
17
+ throw new Error(
18
+ `[jotai-state-tree] Value '${String(snapshot)}' is not a valid '${name}'`
19
+ );
20
+ }
21
+ return snapshot;
22
+ },
23
+ is(value) {
24
+ return validator(value);
25
+ },
26
+ validate(value, context) {
27
+ if (validator(value)) {
28
+ return { valid: true, errors: [] };
29
+ }
30
+ return {
31
+ valid: false,
32
+ errors: [
33
+ {
34
+ context,
35
+ value,
36
+ message: `Value '${String(value)}' is not a valid '${name}'`
37
+ }
38
+ ]
39
+ };
40
+ }
41
+ };
42
+ }
43
+ var string = createSimpleType(
44
+ "string",
45
+ (value) => typeof value === "string"
46
+ );
47
+ var number = createSimpleType(
48
+ "number",
49
+ (value) => typeof value === "number" && !isNaN(value)
50
+ );
51
+ var integer = createSimpleType(
52
+ "integer",
53
+ (value) => typeof value === "number" && !isNaN(value) && Number.isInteger(value)
54
+ );
55
+ var boolean = createSimpleType(
56
+ "boolean",
57
+ (value) => typeof value === "boolean"
58
+ );
59
+ var DatePrimitive = {
60
+ name: "Date",
61
+ _kind: "simple",
62
+ _C: void 0,
63
+ _S: void 0,
64
+ _T: void 0,
65
+ create(snapshot) {
66
+ if (snapshot === void 0) {
67
+ return /* @__PURE__ */ new Date();
68
+ }
69
+ if (snapshot instanceof Date) {
70
+ return snapshot;
71
+ }
72
+ if (typeof snapshot === "number") {
73
+ return new Date(snapshot);
74
+ }
75
+ throw new Error(`[jotai-state-tree] Value is not a valid Date`);
76
+ },
77
+ is(value) {
78
+ return value instanceof Date;
79
+ },
80
+ validate(value, context) {
81
+ if (value instanceof Date || typeof value === "number") {
82
+ return { valid: true, errors: [] };
83
+ }
84
+ return {
85
+ valid: false,
86
+ errors: [
87
+ {
88
+ context,
89
+ value,
90
+ message: "Value is not a valid Date"
91
+ }
92
+ ]
93
+ };
94
+ }
95
+ };
96
+ var nullType = createSimpleType(
97
+ "null",
98
+ (value) => value === null
99
+ );
100
+ var undefinedType = createSimpleType(
101
+ "undefined",
102
+ (value) => value === void 0
103
+ );
104
+ var identifier = {
105
+ ...createSimpleType(
106
+ "identifier",
107
+ (value) => typeof value === "string"
108
+ ),
109
+ _kind: "identifier",
110
+ identifierAttribute: "id"
111
+ };
112
+ var identifierNumber = {
113
+ ...createSimpleType(
114
+ "identifierNumber",
115
+ (value) => typeof value === "number" && !isNaN(value)
116
+ ),
117
+ _kind: "identifierNumber",
118
+ identifierAttribute: "id"
119
+ };
120
+ function literal(value) {
121
+ return {
122
+ name: `literal(${JSON.stringify(value)})`,
123
+ _kind: "literal",
124
+ _value: value,
125
+ _C: void 0,
126
+ _S: void 0,
127
+ _T: void 0,
128
+ create(snapshot) {
129
+ if (snapshot === void 0) {
130
+ return value;
131
+ }
132
+ if (snapshot !== value) {
133
+ throw new Error(
134
+ `[jotai-state-tree] Value '${String(snapshot)}' is not the literal '${String(value)}'`
135
+ );
136
+ }
137
+ return snapshot;
138
+ },
139
+ is(v) {
140
+ return v === value;
141
+ },
142
+ validate(v, context) {
143
+ if (v === value) {
144
+ return { valid: true, errors: [] };
145
+ }
146
+ return {
147
+ valid: false,
148
+ errors: [
149
+ {
150
+ context,
151
+ value: v,
152
+ message: `Value '${String(v)}' is not the literal '${String(value)}'`
153
+ }
154
+ ]
155
+ };
156
+ }
157
+ };
158
+ }
159
+ function enumeration(nameOrOptions, maybeOptions) {
160
+ const name = typeof nameOrOptions === "string" ? nameOrOptions : "enumeration";
161
+ const options = typeof nameOrOptions === "string" ? maybeOptions : nameOrOptions;
162
+ const optionSet = new Set(options);
163
+ return {
164
+ name,
165
+ _kind: "enumeration",
166
+ _options: options,
167
+ _C: void 0,
168
+ _S: void 0,
169
+ _T: void 0,
170
+ create(snapshot) {
171
+ if (snapshot === void 0) {
172
+ throw new Error(`[jotai-state-tree] A value for enumeration '${name}' is required`);
173
+ }
174
+ if (!optionSet.has(snapshot)) {
175
+ throw new Error(
176
+ `[jotai-state-tree] Value '${snapshot}' is not a valid option for enumeration '${name}'. Expected one of: ${options.join(", ")}`
177
+ );
178
+ }
179
+ return snapshot;
180
+ },
181
+ is(value) {
182
+ return typeof value === "string" && optionSet.has(value);
183
+ },
184
+ validate(value, context) {
185
+ if (typeof value === "string" && optionSet.has(value)) {
186
+ return { valid: true, errors: [] };
187
+ }
188
+ return {
189
+ valid: false,
190
+ errors: [
191
+ {
192
+ context,
193
+ value,
194
+ message: `Value '${String(value)}' is not a valid option. Expected one of: ${options.join(", ")}`
195
+ }
196
+ ]
197
+ };
198
+ }
199
+ };
200
+ }
201
+ function frozen(defaultValue) {
202
+ return {
203
+ name: "frozen",
204
+ _kind: "frozen",
205
+ _C: void 0,
206
+ _S: void 0,
207
+ _T: void 0,
208
+ create(snapshot) {
209
+ if (snapshot === void 0) {
210
+ if (defaultValue !== void 0) {
211
+ return deepFreeze(structuredClone(defaultValue));
212
+ }
213
+ return void 0;
214
+ }
215
+ return deepFreeze(structuredClone(snapshot));
216
+ },
217
+ is(value) {
218
+ return true;
219
+ },
220
+ validate() {
221
+ return { valid: true, errors: [] };
222
+ }
223
+ };
224
+ }
225
+ function deepFreeze(obj) {
226
+ if (obj === null || typeof obj !== "object") {
227
+ return obj;
228
+ }
229
+ Object.freeze(obj);
230
+ if (Array.isArray(obj)) {
231
+ obj.forEach(deepFreeze);
232
+ } else {
233
+ Object.values(obj).forEach(deepFreeze);
234
+ }
235
+ return obj;
236
+ }
237
+ function custom(options) {
238
+ return {
239
+ name: options.name,
240
+ _kind: "simple",
241
+ _C: void 0,
242
+ _S: void 0,
243
+ _T: void 0,
244
+ create(snapshot) {
245
+ if (snapshot === void 0) {
246
+ throw new Error(`[jotai-state-tree] A value for custom type '${options.name}' is required`);
247
+ }
248
+ return options.fromSnapshot(snapshot);
249
+ },
250
+ is(value) {
251
+ return options.isTargetType(value);
252
+ },
253
+ validate(value, context) {
254
+ if (options.isTargetType(value)) {
255
+ return { valid: true, errors: [] };
256
+ }
257
+ return {
258
+ valid: false,
259
+ errors: [
260
+ {
261
+ context,
262
+ value,
263
+ message: options.getValidationMessage(value)
264
+ }
265
+ ]
266
+ };
267
+ }
268
+ };
269
+ }
270
+ var finite = createSimpleType(
271
+ "finite",
272
+ (value) => typeof value === "number" && isFinite(value)
273
+ );
274
+ var float = number;
275
+
276
+ // src/tree.ts
277
+ import { atom, createStore } from "jotai";
278
+ var lifecycleHookHandlers = {};
279
+ function setLifecycleHookHandlers(handlers) {
280
+ lifecycleHookHandlers = handlers;
281
+ }
282
+ var isApplyingSnapshotOrPatch = false;
283
+ function getIsApplyingSnapshotOrPatch() {
284
+ return isApplyingSnapshotOrPatch;
285
+ }
286
+ function setIsApplyingSnapshotOrPatch(value) {
287
+ isApplyingSnapshotOrPatch = value;
288
+ }
289
+ var globalStore = createStore();
290
+ function getGlobalStore() {
291
+ return globalStore;
292
+ }
293
+ function setGlobalStore(store) {
294
+ globalStore = store;
295
+ }
296
+ function resetGlobalStore() {
297
+ globalStore = createStore();
298
+ }
299
+ var activeTrackingFn = null;
300
+ function getActiveTrackingFn() {
301
+ return activeTrackingFn;
302
+ }
303
+ function setActiveTrackingFn(fn) {
304
+ activeTrackingFn = fn;
305
+ }
306
+ function trackNodeAccess(node) {
307
+ if (activeTrackingFn) {
308
+ activeTrackingFn(node);
309
+ }
310
+ }
311
+ var nodeRegistry = /* @__PURE__ */ new Map();
312
+ var nodeFinalizationRegistry = new FinalizationRegistry((nodeId) => {
313
+ nodeRegistry.delete(nodeId);
314
+ });
315
+ var identifierRegistry = /* @__PURE__ */ new Map();
316
+ var identifierFinalizationRegistry = new FinalizationRegistry(
317
+ (info) => {
318
+ const typeMap = identifierRegistry.get(info.typeName);
319
+ if (typeMap) {
320
+ const ref = typeMap.get(info.identifier);
321
+ if (!ref || ref.deref() === void 0) {
322
+ typeMap.delete(info.identifier);
323
+ if (typeMap.size === 0) {
324
+ identifierRegistry.delete(info.typeName);
325
+ }
326
+ }
327
+ }
328
+ }
329
+ );
330
+ var nodeIdCounter = 0;
331
+ function generateNodeId() {
332
+ return `node_${++nodeIdCounter}_${Date.now().toString(36)}`;
333
+ }
334
+ var lifecycleListeners = /* @__PURE__ */ new WeakMap();
335
+ function onLifecycleChange(node, listener) {
336
+ let listeners = lifecycleListeners.get(node);
337
+ if (!listeners) {
338
+ listeners = /* @__PURE__ */ new Set();
339
+ lifecycleListeners.set(node, listeners);
340
+ }
341
+ listeners.add(listener);
342
+ return () => {
343
+ listeners?.delete(listener);
344
+ };
345
+ }
346
+ function notifyLifecycleChange(node, isAlive2) {
347
+ const listeners = lifecycleListeners.get(node);
348
+ if (listeners) {
349
+ listeners.forEach((listener) => listener(isAlive2));
350
+ }
351
+ }
352
+ function cloneAndSerialize(value) {
353
+ if (value === null || value === void 0) {
354
+ return value;
355
+ }
356
+ if (hasStateTreeNode(value)) {
357
+ return getSnapshotFromNode(getStateTreeNode(value));
358
+ }
359
+ if (Array.isArray(value)) {
360
+ return value.map(cloneAndSerialize);
361
+ }
362
+ if (typeof value === "object") {
363
+ if (value instanceof Map) {
364
+ const res = {};
365
+ for (const [k, v] of value.entries()) {
366
+ res[k] = cloneAndSerialize(v);
367
+ }
368
+ return res;
369
+ }
370
+ const proto = Object.getPrototypeOf(value);
371
+ if (proto === null || proto === Object.prototype) {
372
+ const res = {};
373
+ for (const [k, v] of Object.entries(value)) {
374
+ res[k] = cloneAndSerialize(v);
375
+ }
376
+ return res;
377
+ }
378
+ }
379
+ return value;
380
+ }
381
+ var StateTreeNode = class {
382
+ constructor(type, initialValue, env, parent, pathSegment) {
383
+ this.$parent = null;
384
+ this.$path = "";
385
+ this.$isAlive = true;
386
+ this.$isApplyingHistory = false;
387
+ /** Child nodes - uses Map but children are explicitly destroyed */
388
+ this.children = /* @__PURE__ */ new Map();
389
+ /** Snapshot listeners */
390
+ this.snapshotListeners = /* @__PURE__ */ new Set();
391
+ /** Patch listeners */
392
+ this.patchListeners = /* @__PURE__ */ new Set();
393
+ /** Volatile state (non-serialized) */
394
+ this.volatileState = {};
395
+ /** Cached snapshot for structural sharing optimization */
396
+ this.cachedSnapshot = void 0;
397
+ this.isSnapshotDirty = true;
398
+ /** Strong reference to the instance proxy to prevent GC of active nodes */
399
+ this.instance = null;
400
+ this.$id = generateNodeId();
401
+ this.$type = type;
402
+ this.$env = env ?? parent?.$env;
403
+ this.$parent = parent ?? null;
404
+ this.$path = parent ? `${parent.$path}/${pathSegment}` : "";
405
+ this.valueAtom = atom(initialValue);
406
+ this.isAliveAtom = atom(true);
407
+ this.snapshotAtom = atom((get) => {
408
+ const value = get(this.valueAtom);
409
+ if (this.$type._kind === "model") {
410
+ const res = {};
411
+ for (const [key, childNode] of this.children.entries()) {
412
+ res[key] = get(childNode.snapshotAtom);
413
+ }
414
+ if (value && typeof value === "object") {
415
+ for (const key of Object.keys(value)) {
416
+ if (!this.children.has(key)) {
417
+ res[key] = value[key];
418
+ }
419
+ }
420
+ }
421
+ return this.postProcessor ? this.postProcessor(res) : res;
422
+ }
423
+ if (this.$type._kind === "array") {
424
+ const res = [];
425
+ const childrenKeys = Array.from(this.children.keys()).sort((a, b) => Number(a) - Number(b));
426
+ for (const key of childrenKeys) {
427
+ const childNode = this.children.get(key);
428
+ res.push(get(childNode.snapshotAtom));
429
+ }
430
+ return res;
431
+ }
432
+ if (this.$type._kind === "map") {
433
+ const res = {};
434
+ for (const [key, childNode] of this.children.entries()) {
435
+ res[key] = get(childNode.snapshotAtom);
436
+ }
437
+ return res;
438
+ }
439
+ return value;
440
+ });
441
+ nodeRegistry.set(this.$id, { node: new WeakRef(this), instance: null });
442
+ nodeFinalizationRegistry.register(this, this.$id, this);
443
+ }
444
+ invalidateSnapshot() {
445
+ if (!this.isSnapshotDirty) {
446
+ this.isSnapshotDirty = true;
447
+ this.cachedSnapshot = void 0;
448
+ if (this.$parent) {
449
+ this.$parent.invalidateSnapshot();
450
+ }
451
+ }
452
+ }
453
+ /** Set the instance reference */
454
+ setInstance(instance) {
455
+ this.instance = instance;
456
+ const entry = nodeRegistry.get(this.$id);
457
+ if (entry && instance && typeof instance === "object") {
458
+ entry.instance = new WeakRef(instance);
459
+ }
460
+ }
461
+ /** Get the instance */
462
+ getInstance() {
463
+ return this.instance;
464
+ }
465
+ /** Get current value from atom */
466
+ getValue() {
467
+ return globalStore.get(this.valueAtom);
468
+ }
469
+ /** Set value on atom */
470
+ setValue(value) {
471
+ if (!this.$isAlive) {
472
+ throw new Error(
473
+ `[jotai-state-tree] Cannot modify a node that is no longer part of the state tree. (Node type: '${this.$type.name}', Path: '${this.$path}')`
474
+ );
475
+ }
476
+ const oldValue = this.getValue();
477
+ if (oldValue === value) {
478
+ return;
479
+ }
480
+ globalStore.set(this.valueAtom, value);
481
+ this.notifySnapshotChange();
482
+ this.notifyPatch(
483
+ { op: "replace", path: this.$path, value },
484
+ { op: "replace", path: this.$path, value: oldValue, oldValue }
485
+ );
486
+ }
487
+ /** Add a child node */
488
+ addChild(key, child) {
489
+ child.$parent = this;
490
+ const newPath = `${this.$path}/${key}`;
491
+ if (child.$path !== newPath) {
492
+ this.updatePathRecursively(child, newPath);
493
+ }
494
+ child.$env = child.$env ?? this.$env;
495
+ this.children.set(key, child);
496
+ lifecycleHookHandlers.runAfterAttach?.(child);
497
+ this.invalidateSnapshot();
498
+ }
499
+ /** Recursively update the path of a node and all its children */
500
+ updatePathRecursively(node, newPath) {
501
+ node.$path = newPath;
502
+ for (const [childKey, childNode] of node.children) {
503
+ const childNewPath = `${newPath}/${childKey}`;
504
+ this.updatePathRecursively(childNode, childNewPath);
505
+ }
506
+ }
507
+ /** Remove a child node */
508
+ removeChild(key) {
509
+ const child = this.children.get(key);
510
+ if (child) {
511
+ child.destroy();
512
+ this.children.delete(key);
513
+ this.invalidateSnapshot();
514
+ }
515
+ }
516
+ /** Get a child node */
517
+ getChild(key) {
518
+ return this.children.get(key);
519
+ }
520
+ /** Get all children */
521
+ getChildren() {
522
+ return this.children;
523
+ }
524
+ /** Register identifier */
525
+ registerIdentifier(typeName, identifier2) {
526
+ this.identifierTypeName = typeName;
527
+ this.identifierValue = identifier2;
528
+ let typeMap = identifierRegistry.get(typeName);
529
+ if (!typeMap) {
530
+ typeMap = /* @__PURE__ */ new Map();
531
+ identifierRegistry.set(typeName, typeMap);
532
+ }
533
+ typeMap.set(identifier2, new WeakRef(this));
534
+ identifierFinalizationRegistry.register(
535
+ this,
536
+ { typeName, identifier: identifier2 },
537
+ this
538
+ );
539
+ }
540
+ /** Unregister identifier */
541
+ unregisterIdentifier() {
542
+ if (this.identifierTypeName !== void 0 && this.identifierValue !== void 0) {
543
+ const typeMap = identifierRegistry.get(this.identifierTypeName);
544
+ if (typeMap) {
545
+ typeMap.delete(this.identifierValue);
546
+ if (typeMap.size === 0) {
547
+ identifierRegistry.delete(this.identifierTypeName);
548
+ }
549
+ }
550
+ identifierFinalizationRegistry.unregister(this);
551
+ }
552
+ }
553
+ /** Subscribe to snapshot changes */
554
+ onSnapshot(listener) {
555
+ this.snapshotListeners.add(listener);
556
+ return () => {
557
+ this.snapshotListeners.delete(listener);
558
+ };
559
+ }
560
+ /** Subscribe to patches */
561
+ onPatch(listener) {
562
+ this.patchListeners.add(listener);
563
+ return () => {
564
+ this.patchListeners.delete(listener);
565
+ };
566
+ }
567
+ /** Notify patch listeners */
568
+ notifyPatch(patch, reversePatch) {
569
+ const serializedPatch = {
570
+ ...patch,
571
+ value: "value" in patch ? cloneAndSerialize(patch.value) : void 0
572
+ };
573
+ if (!("value" in patch)) {
574
+ delete serializedPatch.value;
575
+ }
576
+ const serializedReversePatch = {
577
+ ...reversePatch,
578
+ value: "value" in reversePatch ? cloneAndSerialize(reversePatch.value) : void 0,
579
+ oldValue: "oldValue" in reversePatch ? cloneAndSerialize(reversePatch.oldValue) : void 0
580
+ };
581
+ if (!("value" in reversePatch)) {
582
+ delete serializedReversePatch.value;
583
+ }
584
+ if (!("oldValue" in reversePatch)) {
585
+ delete serializedReversePatch.oldValue;
586
+ }
587
+ this.patchListeners.forEach((listener) => listener(serializedPatch, serializedReversePatch));
588
+ if (this.$parent) {
589
+ this.$parent.notifyPatch(serializedPatch, serializedReversePatch);
590
+ }
591
+ }
592
+ /** Notify snapshot listeners */
593
+ notifySnapshotChange() {
594
+ let current = this;
595
+ while (current) {
596
+ current.invalidateSnapshot();
597
+ const snapshot = getSnapshotFromNode(current);
598
+ current.snapshotListeners.forEach((listener) => listener(snapshot));
599
+ current = current.$parent;
600
+ }
601
+ }
602
+ /** Notify about a property change (for use by model proxy) */
603
+ notifyPropertyChange(propName, newValue, oldValue) {
604
+ const path = this.$path ? `${this.$path}/${propName}` : `/${propName}`;
605
+ this.notifySnapshotChange();
606
+ this.notifyPatch(
607
+ { op: "replace", path, value: newValue },
608
+ { op: "replace", path, value: oldValue, oldValue }
609
+ );
610
+ }
611
+ /** Notify about a volatile state change (triggers snapshot listeners without patches) */
612
+ notifyVolatileChange() {
613
+ this.notifySnapshotChange();
614
+ }
615
+ /** Get root node */
616
+ getRoot() {
617
+ let node = this;
618
+ while (node.$parent) {
619
+ node = node.$parent;
620
+ }
621
+ return node;
622
+ }
623
+ /** Destroy this node and all children */
624
+ destroy() {
625
+ if (!this.$isAlive) return;
626
+ this.instance = null;
627
+ lifecycleHookHandlers.runBeforeDestroy?.(this);
628
+ this.children.forEach((child) => child.destroy());
629
+ this.children.clear();
630
+ this.unregisterIdentifier();
631
+ this.$isAlive = false;
632
+ try {
633
+ globalStore.set(this.isAliveAtom, false);
634
+ } catch (e) {
635
+ }
636
+ notifyLifecycleChange(this, false);
637
+ nodeRegistry.delete(this.$id);
638
+ nodeFinalizationRegistry.unregister(this);
639
+ lifecycleListeners.delete(this);
640
+ this.snapshotListeners.forEach((listener) => {
641
+ try {
642
+ listener(void 0);
643
+ } catch (e) {
644
+ }
645
+ });
646
+ try {
647
+ globalStore.set(this.valueAtom, void 0);
648
+ } catch (e) {
649
+ }
650
+ this.snapshotListeners.clear();
651
+ this.patchListeners.clear();
652
+ }
653
+ /** Detach from parent */
654
+ detach() {
655
+ if (this.$parent) {
656
+ lifecycleHookHandlers.runBeforeDetach?.(this);
657
+ const parent = this.$parent;
658
+ for (const [key, child] of this.$parent.children) {
659
+ if (child === this) {
660
+ this.$parent.children.delete(key);
661
+ break;
662
+ }
663
+ }
664
+ this.$parent = null;
665
+ this.$path = "";
666
+ parent.invalidateSnapshot();
667
+ }
668
+ }
669
+ };
670
+ var $treenode = /* @__PURE__ */ Symbol.for("jotai-state-tree-node");
671
+ function getStateTreeNode(instance) {
672
+ if (instance && typeof instance === "object" && $treenode in instance) {
673
+ return instance[$treenode];
674
+ }
675
+ throw new Error("[jotai-state-tree] Value is not a state tree node");
676
+ }
677
+ function hasStateTreeNode(instance) {
678
+ return instance !== null && typeof instance === "object" && $treenode in instance;
679
+ }
680
+ function getSnapshotFromNode(node) {
681
+ if (!node.isSnapshotDirty && node.cachedSnapshot !== void 0) {
682
+ return node.cachedSnapshot;
683
+ }
684
+ const type = node.$type;
685
+ const value = node.getValue();
686
+ let snapshot;
687
+ if (type._kind === "model") {
688
+ const modelSnapshot = {};
689
+ const children = node.getChildren();
690
+ for (const [key, childNode] of children) {
691
+ modelSnapshot[key] = getSnapshotFromNode(childNode);
692
+ }
693
+ if (node.postProcessor) {
694
+ snapshot = node.postProcessor(modelSnapshot);
695
+ } else {
696
+ snapshot = modelSnapshot;
697
+ }
698
+ } else if (type._kind === "array") {
699
+ const arr = value;
700
+ snapshot = arr.map((_, index) => {
701
+ const childNode = node.getChild(String(index));
702
+ return childNode ? getSnapshotFromNode(childNode) : arr[index];
703
+ });
704
+ } else if (type._kind === "map") {
705
+ const mapSnapshot = {};
706
+ const children = node.getChildren();
707
+ for (const [key, childNode] of children) {
708
+ mapSnapshot[key] = getSnapshotFromNode(childNode);
709
+ }
710
+ snapshot = mapSnapshot;
711
+ } else if (type._kind === "reference") {
712
+ snapshot = node.identifierValue ?? value;
713
+ } else {
714
+ snapshot = value;
715
+ }
716
+ node.cachedSnapshot = snapshot;
717
+ node.isSnapshotDirty = false;
718
+ return snapshot;
719
+ }
720
+ function applySnapshotToNode(node, snapshot) {
721
+ if (!node.$isAlive) {
722
+ throw new Error("[jotai-state-tree] Cannot apply snapshot to a dead node");
723
+ }
724
+ const type = node.$type;
725
+ if (node.preProcessor) {
726
+ snapshot = node.preProcessor(snapshot);
727
+ }
728
+ if (type._kind === "model" && typeof snapshot === "object" && snapshot !== null) {
729
+ const snapshotObj = snapshot;
730
+ const children = node.getChildren();
731
+ for (const [key, childNode] of children) {
732
+ if (key in snapshotObj) {
733
+ applySnapshotToNode(childNode, snapshotObj[key]);
734
+ }
735
+ }
736
+ } else if (type._kind === "array" && Array.isArray(snapshot)) {
737
+ const mstArray = node.getInstance();
738
+ if (mstArray) {
739
+ mstArray.replace(snapshot);
740
+ } else {
741
+ node.setValue(snapshot);
742
+ }
743
+ } else if (type._kind === "map" && typeof snapshot === "object" && snapshot !== null) {
744
+ const mstMap = node.getInstance();
745
+ if (mstMap) {
746
+ mstMap.replace(snapshot);
747
+ } else {
748
+ node.setValue(snapshot);
749
+ }
750
+ } else {
751
+ node.setValue(snapshot);
752
+ }
753
+ }
754
+ function resolveIdentifier(typeName, identifier2) {
755
+ const weakRef = identifierRegistry.get(typeName)?.get(identifier2);
756
+ return weakRef?.deref();
757
+ }
758
+ function getRegistryStats() {
759
+ let liveNodeCount = 0;
760
+ let staleNodeCount = 0;
761
+ for (const entry of nodeRegistry.values()) {
762
+ const node = entry.node.deref();
763
+ if (node && node.$isAlive) {
764
+ liveNodeCount++;
765
+ } else {
766
+ staleNodeCount++;
767
+ }
768
+ }
769
+ let identifierCount = 0;
770
+ for (const typeMap of identifierRegistry.values()) {
771
+ for (const weakRef of typeMap.values()) {
772
+ const node = weakRef.deref();
773
+ if (node && node.$isAlive) {
774
+ identifierCount++;
775
+ }
776
+ }
777
+ }
778
+ return {
779
+ nodeRegistrySize: nodeRegistry.size,
780
+ identifierRegistrySize: identifierCount,
781
+ identifierTypeCount: identifierRegistry.size,
782
+ liveNodeCount,
783
+ staleNodeCount
784
+ };
785
+ }
786
+ function cleanupStaleEntries() {
787
+ let cleaned = 0;
788
+ for (const [id, entry] of nodeRegistry.entries()) {
789
+ if (!entry.node.deref()) {
790
+ nodeRegistry.delete(id);
791
+ cleaned++;
792
+ }
793
+ }
794
+ for (const [typeName, typeMap] of identifierRegistry.entries()) {
795
+ for (const [identifier2, weakRef] of typeMap.entries()) {
796
+ if (!weakRef.deref()) {
797
+ typeMap.delete(identifier2);
798
+ cleaned++;
799
+ }
800
+ }
801
+ if (typeMap.size === 0) {
802
+ identifierRegistry.delete(typeName);
803
+ }
804
+ }
805
+ return cleaned;
806
+ }
807
+ function clearAllRegistries() {
808
+ for (const entry of nodeRegistry.values()) {
809
+ const node = entry.node.deref();
810
+ if (node) {
811
+ node.$isAlive = false;
812
+ try {
813
+ globalStore.set(node.isAliveAtom, false);
814
+ } catch (e) {
815
+ }
816
+ }
817
+ }
818
+ nodeRegistry.clear();
819
+ identifierRegistry.clear();
820
+ nodeIdCounter = 0;
821
+ }
822
+ function getRoot(target) {
823
+ const node = getStateTreeNode(target);
824
+ const rootNode = node.getRoot();
825
+ return rootNode.getInstance();
826
+ }
827
+ function getParent(target, depth = 1) {
828
+ let node = getStateTreeNode(target);
829
+ for (let i = 0; i < depth; i++) {
830
+ if (!node.$parent) {
831
+ throw new Error("[jotai-state-tree] Cannot get parent of root node");
832
+ }
833
+ node = node.$parent;
834
+ }
835
+ return node.getInstance();
836
+ }
837
+ function tryGetParent(target, depth = 1) {
838
+ try {
839
+ return getParent(target, depth);
840
+ } catch {
841
+ return void 0;
842
+ }
843
+ }
844
+ function hasParent(target, depth = 1) {
845
+ let node = getStateTreeNode(target);
846
+ for (let i = 0; i < depth; i++) {
847
+ if (!node.$parent) return false;
848
+ node = node.$parent;
849
+ }
850
+ return true;
851
+ }
852
+ function getParentOfType(target, type) {
853
+ let node = getStateTreeNode(target).$parent;
854
+ while (node) {
855
+ if (node.$type === type || node.$type.name === type.name) {
856
+ return node.getInstance();
857
+ }
858
+ node = node.$parent;
859
+ }
860
+ throw new Error(`[jotai-state-tree] No parent of type '${type.name}' found`);
861
+ }
862
+ function getPath(target) {
863
+ return getStateTreeNode(target).$path;
864
+ }
865
+ function getPathParts(target) {
866
+ const path = getPath(target);
867
+ return path ? path.split("/").filter(Boolean) : [];
868
+ }
869
+ function getEnv(target) {
870
+ return getStateTreeNode(target).$env;
871
+ }
872
+ function isAlive(target) {
873
+ try {
874
+ return getStateTreeNode(target).$isAlive;
875
+ } catch {
876
+ return false;
877
+ }
878
+ }
879
+ function isRoot(target) {
880
+ return getStateTreeNode(target).$parent === null;
881
+ }
882
+ function getType(target) {
883
+ return getStateTreeNode(target).$type;
884
+ }
885
+ function isStateTreeNode(value) {
886
+ return hasStateTreeNode(value);
887
+ }
888
+ function getIdentifier(target) {
889
+ const node = getStateTreeNode(target);
890
+ return node.identifierValue ?? null;
891
+ }
892
+ function destroy(target) {
893
+ const node = getStateTreeNode(target);
894
+ node.destroy();
895
+ }
896
+ function detach(target) {
897
+ const node = getStateTreeNode(target);
898
+ node.detach();
899
+ return target;
900
+ }
901
+ function clone(target, keepEnvironment = true) {
902
+ const node = getStateTreeNode(target);
903
+ const snapshot = getSnapshotFromNode(node);
904
+ const type = node.$type;
905
+ return type.create(snapshot, keepEnvironment ? node.$env : void 0);
906
+ }
907
+ function getSnapshot(target) {
908
+ const node = getStateTreeNode(target);
909
+ trackNodeAccess(node);
910
+ return getSnapshotFromNode(node);
911
+ }
912
+ function applySnapshot(target, snapshot) {
913
+ const node = getStateTreeNode(target);
914
+ const wasApplying = getIsApplyingSnapshotOrPatch();
915
+ setIsApplyingSnapshotOrPatch(true);
916
+ try {
917
+ applySnapshotToNode(node, snapshot);
918
+ } finally {
919
+ setIsApplyingSnapshotOrPatch(wasApplying);
920
+ }
921
+ }
922
+ function onSnapshot(target, listener) {
923
+ const node = getStateTreeNode(target);
924
+ return node.onSnapshot(listener);
925
+ }
926
+ function onPatch(target, listener) {
927
+ const node = getStateTreeNode(target);
928
+ return node.onPatch(listener);
929
+ }
930
+ function applyPatch(target, patch) {
931
+ const patches = Array.isArray(patch) ? patch : [patch];
932
+ const rootNode = getStateTreeNode(target).getRoot();
933
+ const wasApplying = getIsApplyingSnapshotOrPatch();
934
+ setIsApplyingSnapshotOrPatch(true);
935
+ try {
936
+ for (const p of patches) {
937
+ applyPatchToNode(rootNode, p);
938
+ }
939
+ } finally {
940
+ setIsApplyingSnapshotOrPatch(wasApplying);
941
+ }
942
+ }
943
+ function applyPatchToNode(rootNode, patch) {
944
+ const pathParts = patch.path.split("/").filter(Boolean);
945
+ let node = rootNode;
946
+ for (let i = 0; i < pathParts.length - 1; i++) {
947
+ const childNode = node.getChild(pathParts[i]);
948
+ if (!childNode) {
949
+ throw new Error(`[jotai-state-tree] Invalid patch path: ${patch.path}`);
950
+ }
951
+ node = childNode;
952
+ }
953
+ const key = pathParts[pathParts.length - 1];
954
+ switch (patch.op) {
955
+ case "replace": {
956
+ const instance = node.getInstance();
957
+ if (instance && Array.isArray(instance)) {
958
+ const index = parseInt(key, 10);
959
+ instance[index] = patch.value;
960
+ } else if (instance && instance instanceof Map) {
961
+ instance.set(key, patch.value);
962
+ } else {
963
+ const childNode = node.getChild(key);
964
+ if (childNode) {
965
+ applySnapshotToNode(childNode, patch.value);
966
+ } else {
967
+ const currentValue = node.getValue();
968
+ currentValue[key] = patch.value;
969
+ node.setValue(currentValue);
970
+ }
971
+ }
972
+ break;
973
+ }
974
+ case "add": {
975
+ const instance = node.getInstance();
976
+ if (instance && Array.isArray(instance)) {
977
+ const index = key === "-" ? instance.length : parseInt(key, 10);
978
+ instance.splice(index, 0, patch.value);
979
+ } else if (instance && instance instanceof Map) {
980
+ instance.set(key, patch.value);
981
+ } else {
982
+ const currentValue = node.getValue();
983
+ if (Array.isArray(currentValue)) {
984
+ const index = key === "-" ? currentValue.length : parseInt(key, 10);
985
+ currentValue.splice(index, 0, patch.value);
986
+ node.setValue([...currentValue]);
987
+ } else if (typeof currentValue === "object" && currentValue !== null) {
988
+ currentValue[key] = patch.value;
989
+ node.setValue({ ...currentValue });
990
+ }
991
+ }
992
+ break;
993
+ }
994
+ case "remove": {
995
+ const instance = node.getInstance();
996
+ if (instance && Array.isArray(instance)) {
997
+ const index = parseInt(key, 10);
998
+ instance.splice(index, 1);
999
+ } else if (instance && instance instanceof Map) {
1000
+ instance.delete(key);
1001
+ } else {
1002
+ const currentValue = node.getValue();
1003
+ if (Array.isArray(currentValue)) {
1004
+ const index = parseInt(key, 10);
1005
+ currentValue.splice(index, 1);
1006
+ node.setValue([...currentValue]);
1007
+ } else if (typeof currentValue === "object" && currentValue !== null) {
1008
+ delete currentValue[key];
1009
+ node.setValue({ ...currentValue });
1010
+ }
1011
+ }
1012
+ break;
1013
+ }
1014
+ }
1015
+ }
1016
+ function recordPatches(target) {
1017
+ const patches = [];
1018
+ const inversePatches = [];
1019
+ let recording = true;
1020
+ const disposer = onPatch(target, (patch, reversePatch) => {
1021
+ if (recording) {
1022
+ patches.push(patch);
1023
+ inversePatches.push(reversePatch);
1024
+ }
1025
+ });
1026
+ return {
1027
+ patches,
1028
+ inversePatches,
1029
+ stop: () => {
1030
+ recording = false;
1031
+ disposer();
1032
+ },
1033
+ resume: () => {
1034
+ recording = true;
1035
+ },
1036
+ replay: (t) => {
1037
+ applyPatch(t, patches);
1038
+ },
1039
+ undo: (t) => {
1040
+ applyPatch(t, inversePatches.slice().reverse());
1041
+ }
1042
+ };
1043
+ }
1044
+ var currentAction = null;
1045
+ function getCurrentAction() {
1046
+ return currentAction;
1047
+ }
1048
+ var actionListeners = /* @__PURE__ */ new Set();
1049
+ var actionRecorderHooks = [];
1050
+ function registerActionRecorderHook(hook) {
1051
+ actionRecorderHooks.push(hook);
1052
+ return () => {
1053
+ const index = actionRecorderHooks.indexOf(hook);
1054
+ if (index >= 0) {
1055
+ actionRecorderHooks.splice(index, 1);
1056
+ }
1057
+ };
1058
+ }
1059
+ function isActionRunning() {
1060
+ return currentAction !== null;
1061
+ }
1062
+ function trackAction(node, name, args, fn) {
1063
+ const previousAction = currentAction;
1064
+ currentAction = { name, args, tree: node, parent: previousAction };
1065
+ try {
1066
+ const result = fn();
1067
+ const call = {
1068
+ name,
1069
+ path: node.$path,
1070
+ args
1071
+ };
1072
+ actionListeners.forEach((listener) => listener(call));
1073
+ actionRecorderHooks.forEach((hook) => hook(node, call));
1074
+ return result;
1075
+ } finally {
1076
+ currentAction = previousAction;
1077
+ }
1078
+ }
1079
+ function onAction(target, listener) {
1080
+ actionListeners.add(listener);
1081
+ return () => {
1082
+ actionListeners.delete(listener);
1083
+ };
1084
+ }
1085
+ function walk(target, visitor) {
1086
+ const treeNode = getStateTreeNode(target);
1087
+ function visitNode(node) {
1088
+ const instance = node.getInstance();
1089
+ if (instance) {
1090
+ visitor(instance);
1091
+ }
1092
+ node.getChildren().forEach(visitNode);
1093
+ }
1094
+ visitNode(treeNode);
1095
+ }
1096
+ function getMembers(target) {
1097
+ const result = [];
1098
+ const node = getStateTreeNode(target);
1099
+ const instance = target;
1100
+ for (const [key] of node.getChildren()) {
1101
+ result.push({
1102
+ name: key,
1103
+ type: "property",
1104
+ value: instance[key]
1105
+ });
1106
+ }
1107
+ for (const [key, value] of Object.entries(node.volatileState)) {
1108
+ result.push({
1109
+ name: key,
1110
+ type: "volatile",
1111
+ value
1112
+ });
1113
+ }
1114
+ return result;
1115
+ }
1116
+ function resolvePath(target, path) {
1117
+ const parts = path.split("/").filter(Boolean);
1118
+ let node = getStateTreeNode(target);
1119
+ for (const part of parts) {
1120
+ const child = node.getChild(part);
1121
+ if (!child) {
1122
+ throw new Error(`[jotai-state-tree] Invalid path: ${path}`);
1123
+ }
1124
+ node = child;
1125
+ }
1126
+ return node.getInstance();
1127
+ }
1128
+ function tryResolve(target, path) {
1129
+ try {
1130
+ return resolvePath(target, path);
1131
+ } catch {
1132
+ return void 0;
1133
+ }
1134
+ }
1135
+ function getRelativePath(from, to) {
1136
+ const fromNode = getStateTreeNode(from);
1137
+ const toNode = getStateTreeNode(to);
1138
+ const fromParts = fromNode.$path.split("/").filter(Boolean);
1139
+ const toParts = toNode.$path.split("/").filter(Boolean);
1140
+ let commonLength = 0;
1141
+ for (let i = 0; i < Math.min(fromParts.length, toParts.length); i++) {
1142
+ if (fromParts[i] === toParts[i]) {
1143
+ commonLength++;
1144
+ } else {
1145
+ break;
1146
+ }
1147
+ }
1148
+ const upCount = fromParts.length - commonLength;
1149
+ const downParts = toParts.slice(commonLength);
1150
+ const parts = [];
1151
+ for (let i = 0; i < upCount; i++) {
1152
+ parts.push("..");
1153
+ }
1154
+ parts.push(...downParts);
1155
+ return parts.join("/") || ".";
1156
+ }
1157
+ function isAncestor(ancestor, descendant) {
1158
+ const ancestorNode = getStateTreeNode(ancestor);
1159
+ let currentNode = getStateTreeNode(descendant);
1160
+ while (currentNode) {
1161
+ if (currentNode === ancestorNode) {
1162
+ return true;
1163
+ }
1164
+ currentNode = currentNode.$parent;
1165
+ }
1166
+ return false;
1167
+ }
1168
+ function haveSameRoot(a, b) {
1169
+ return getRoot(a) === getRoot(b);
1170
+ }
1171
+ function findAll(target, predicate) {
1172
+ const results = [];
1173
+ walk(target, (node) => {
1174
+ if (predicate(node)) {
1175
+ results.push(node);
1176
+ }
1177
+ });
1178
+ return results;
1179
+ }
1180
+ function findFirst(target, predicate) {
1181
+ let result;
1182
+ walk(target, (node) => {
1183
+ if (!result && predicate(node)) {
1184
+ result = node;
1185
+ }
1186
+ });
1187
+ return result;
1188
+ }
1189
+ function isValidReference(target, identifier2) {
1190
+ if (!hasStateTreeNode(target)) return false;
1191
+ try {
1192
+ const node = getStateTreeNode(target);
1193
+ const typeName = node.$type.name;
1194
+ const resolved = resolveIdentifier(typeName, identifier2);
1195
+ return resolved !== void 0;
1196
+ } catch {
1197
+ return false;
1198
+ }
1199
+ }
1200
+ function getTreeStats(target) {
1201
+ let nodeCount = 0;
1202
+ let maxDepth = 0;
1203
+ const types = {};
1204
+ walk(target, (node) => {
1205
+ if (!hasStateTreeNode(node)) return;
1206
+ const stateNode = getStateTreeNode(node);
1207
+ nodeCount++;
1208
+ const depth = stateNode.$path.split("/").filter(Boolean).length;
1209
+ maxDepth = Math.max(maxDepth, depth);
1210
+ const typeName = stateNode.$type.name;
1211
+ types[typeName] = (types[typeName] || 0) + 1;
1212
+ });
1213
+ return {
1214
+ nodeCount,
1215
+ depth: maxDepth,
1216
+ types
1217
+ };
1218
+ }
1219
+ function cloneDeep(target) {
1220
+ const snapshot = getSnapshot(target);
1221
+ const node = getStateTreeNode(target);
1222
+ return node.$type.create(snapshot, node.$env);
1223
+ }
1224
+ function getOrCreatePath(target, path, creator) {
1225
+ const parts = path.split("/").filter(Boolean);
1226
+ let node = getStateTreeNode(target);
1227
+ for (let i = 0; i < parts.length; i++) {
1228
+ const part = parts[i];
1229
+ let child = node.getChild(part);
1230
+ if (!child && i === parts.length - 1) {
1231
+ const instance = creator();
1232
+ if (hasStateTreeNode(instance)) {
1233
+ child = getStateTreeNode(instance);
1234
+ node.addChild(part, child);
1235
+ } else {
1236
+ throw new Error(
1237
+ "[jotai-state-tree] Creator must return a state tree node"
1238
+ );
1239
+ }
1240
+ }
1241
+ if (!child) {
1242
+ throw new Error(`[jotai-state-tree] Invalid path: ${path}`);
1243
+ }
1244
+ node = child;
1245
+ }
1246
+ return node.getInstance();
1247
+ }
1248
+ function freeze(target) {
1249
+ const node = getStateTreeNode(target);
1250
+ node.volatileState.$frozen = true;
1251
+ for (const [, child] of node.getChildren()) {
1252
+ const instance = child.getInstance();
1253
+ if (instance && hasStateTreeNode(instance)) {
1254
+ freeze(instance);
1255
+ }
1256
+ }
1257
+ }
1258
+ function isFrozen(target) {
1259
+ const node = getStateTreeNode(target);
1260
+ return node.volatileState.$frozen === true;
1261
+ }
1262
+ function unfreeze(target) {
1263
+ const node = getStateTreeNode(target);
1264
+ delete node.volatileState.$frozen;
1265
+ for (const [, child] of node.getChildren()) {
1266
+ const instance = child.getInstance();
1267
+ if (instance && hasStateTreeNode(instance)) {
1268
+ unfreeze(instance);
1269
+ }
1270
+ }
1271
+ }
1272
+
1273
+ // src/lifecycle.ts
1274
+ var nodeHooks = /* @__PURE__ */ new WeakMap();
1275
+ function registerHooks(node, hooks) {
1276
+ const existing = nodeHooks.get(node) || {};
1277
+ const filteredHooks = {};
1278
+ for (const [key, value] of Object.entries(hooks)) {
1279
+ if (value !== void 0) {
1280
+ const existingHook = existing[key];
1281
+ if (existingHook) {
1282
+ filteredHooks[key] = () => {
1283
+ existingHook();
1284
+ value();
1285
+ };
1286
+ } else {
1287
+ filteredHooks[key] = value;
1288
+ }
1289
+ }
1290
+ }
1291
+ const merged = { ...existing, ...filteredHooks };
1292
+ nodeHooks.set(node, merged);
1293
+ }
1294
+ function runAfterCreate(node) {
1295
+ const hooks = nodeHooks.get(node);
1296
+ if (hooks?.afterCreate) {
1297
+ hooks.afterCreate();
1298
+ }
1299
+ }
1300
+ function runAfterAttach(node) {
1301
+ const hooks = nodeHooks.get(node);
1302
+ if (hooks?.afterAttach) {
1303
+ hooks.afterAttach();
1304
+ }
1305
+ }
1306
+ function runBeforeDetach(node) {
1307
+ const hooks = nodeHooks.get(node);
1308
+ if (hooks?.beforeDetach) {
1309
+ hooks.beforeDetach();
1310
+ }
1311
+ }
1312
+ function runBeforeDestroy(node) {
1313
+ const hooks = nodeHooks.get(node);
1314
+ if (hooks?.beforeDestroy) {
1315
+ hooks.beforeDestroy();
1316
+ }
1317
+ }
1318
+ var activeMiddlewareEvent = null;
1319
+ var middlewareIdCounter = 0;
1320
+ var middlewareStack = [];
1321
+ var nodeMiddlewares = /* @__PURE__ */ new WeakMap();
1322
+ function addMiddleware(target, handler, includeHooks = true) {
1323
+ if (target && isStateTreeNode(target)) {
1324
+ const node = getStateTreeNode(target);
1325
+ let middlewares = nodeMiddlewares.get(node);
1326
+ if (!middlewares) {
1327
+ middlewares = /* @__PURE__ */ new Set();
1328
+ nodeMiddlewares.set(node, middlewares);
1329
+ }
1330
+ middlewares.add(handler);
1331
+ return () => {
1332
+ const current = nodeMiddlewares.get(node);
1333
+ if (current) {
1334
+ current.delete(handler);
1335
+ }
1336
+ };
1337
+ }
1338
+ middlewareStack.push(handler);
1339
+ return () => {
1340
+ const index = middlewareStack.indexOf(handler);
1341
+ if (index >= 0) {
1342
+ middlewareStack.splice(index, 1);
1343
+ }
1344
+ };
1345
+ }
1346
+ function getMiddlewaresForNode(node) {
1347
+ const handlers = [];
1348
+ let current = node;
1349
+ while (current) {
1350
+ const middlewares = nodeMiddlewares.get(current);
1351
+ if (middlewares) {
1352
+ handlers.unshift(...Array.from(middlewares));
1353
+ }
1354
+ current = current.$parent;
1355
+ }
1356
+ return [...Array.from(middlewareStack), ...handlers];
1357
+ }
1358
+ function runMiddlewares(node, event, fn) {
1359
+ const handlers = getMiddlewaresForNode(node);
1360
+ if (handlers.length === 0) {
1361
+ const previousEvent = activeMiddlewareEvent;
1362
+ activeMiddlewareEvent = event;
1363
+ try {
1364
+ return fn(event);
1365
+ } finally {
1366
+ activeMiddlewareEvent = previousEvent;
1367
+ }
1368
+ }
1369
+ let index = 0;
1370
+ let aborted = false;
1371
+ let abortValue;
1372
+ const abort = (value) => {
1373
+ aborted = true;
1374
+ abortValue = value;
1375
+ return value;
1376
+ };
1377
+ const next = (call, callback) => {
1378
+ if (aborted) return abortValue;
1379
+ const previousEvent = activeMiddlewareEvent;
1380
+ activeMiddlewareEvent = call;
1381
+ try {
1382
+ if (index >= handlers.length) {
1383
+ const result = fn(call);
1384
+ return callback ? callback(result) : result;
1385
+ }
1386
+ const middleware = handlers[index++];
1387
+ return middleware(call, next, abort);
1388
+ } finally {
1389
+ activeMiddlewareEvent = previousEvent;
1390
+ }
1391
+ };
1392
+ return next(event);
1393
+ }
1394
+ function createMiddlewareRunner(node, actionName, args) {
1395
+ const id = ++middlewareIdCounter;
1396
+ const parentEvent = activeMiddlewareEvent;
1397
+ const event = {
1398
+ type: "action",
1399
+ name: actionName,
1400
+ id,
1401
+ parentId: parentEvent ? parentEvent.id : 0,
1402
+ rootId: parentEvent ? parentEvent.rootId : id,
1403
+ context: node.getInstance(),
1404
+ tree: node.getRoot().getInstance(),
1405
+ args,
1406
+ parentEvent: parentEvent ?? void 0
1407
+ };
1408
+ return (fn) => {
1409
+ return runMiddlewares(node, event, fn);
1410
+ };
1411
+ }
1412
+ function flow(generator) {
1413
+ return function flowAction(...args) {
1414
+ const node = (this && typeof this === "object" && $treenode in this ? this[$treenode] : null) ?? getCurrentAction()?.tree;
1415
+ if (!node) {
1416
+ let step2 = function(nextFn) {
1417
+ let result;
1418
+ try {
1419
+ result = nextFn();
1420
+ } catch (e) {
1421
+ return Promise.reject(e);
1422
+ }
1423
+ if (result.done) {
1424
+ return Promise.resolve(result.value);
1425
+ }
1426
+ return Promise.resolve(result.value).then(
1427
+ (value) => step2(() => gen2.next(value)),
1428
+ (error) => step2(() => gen2.throw(error))
1429
+ );
1430
+ };
1431
+ var step = step2;
1432
+ const gen2 = generator.apply(this, args);
1433
+ return step2(() => gen2.next(void 0));
1434
+ }
1435
+ const actionName = getCurrentAction()?.name ?? "anonymousFlow";
1436
+ const gen = generator.apply(this, args);
1437
+ const actionEvent = activeMiddlewareEvent;
1438
+ const spawnEventId = ++middlewareIdCounter;
1439
+ const spawnEvent = {
1440
+ type: "flow_spawn",
1441
+ name: actionName,
1442
+ id: spawnEventId,
1443
+ parentId: actionEvent ? actionEvent.id : 0,
1444
+ rootId: actionEvent ? actionEvent.rootId : spawnEventId,
1445
+ context: node.getInstance(),
1446
+ tree: node.getRoot().getInstance(),
1447
+ args,
1448
+ parentEvent: actionEvent ?? void 0
1449
+ };
1450
+ return new Promise((resolve, reject) => {
1451
+ runMiddlewares(node, spawnEvent, () => {
1452
+ function step2(nextFn, isResume, resumeError) {
1453
+ let result;
1454
+ if (isResume) {
1455
+ if (!node.$isAlive) {
1456
+ gen.return(void 0);
1457
+ handleThrow(new Error("FLOW_CANCELLED"));
1458
+ return;
1459
+ }
1460
+ const resumeEventId = ++middlewareIdCounter;
1461
+ const resumeEvent = {
1462
+ type: resumeError !== void 0 ? "flow_resume_error" : "flow_resume",
1463
+ name: actionName,
1464
+ id: resumeEventId,
1465
+ parentId: spawnEvent.id,
1466
+ rootId: spawnEvent.rootId,
1467
+ context: node.getInstance(),
1468
+ tree: node.getRoot().getInstance(),
1469
+ args: resumeError !== void 0 ? [resumeError] : [],
1470
+ parentEvent: spawnEvent
1471
+ };
1472
+ runMiddlewares(node, resumeEvent, () => {
1473
+ try {
1474
+ result = trackAction(node, actionName, args, () => {
1475
+ return nextFn();
1476
+ });
1477
+ } catch (e) {
1478
+ handleThrow(e);
1479
+ return;
1480
+ }
1481
+ handleResult(result);
1482
+ });
1483
+ } else {
1484
+ try {
1485
+ result = nextFn();
1486
+ } catch (e) {
1487
+ handleThrow(e);
1488
+ return;
1489
+ }
1490
+ handleResult(result);
1491
+ }
1492
+ }
1493
+ function handleResult(result) {
1494
+ if (result.done) {
1495
+ const returnEventId = ++middlewareIdCounter;
1496
+ const returnEvent = {
1497
+ type: "flow_return",
1498
+ name: actionName,
1499
+ id: returnEventId,
1500
+ parentId: spawnEvent.id,
1501
+ rootId: spawnEvent.rootId,
1502
+ context: node.getInstance(),
1503
+ tree: node.getRoot().getInstance(),
1504
+ args: [result.value],
1505
+ parentEvent: spawnEvent
1506
+ };
1507
+ runMiddlewares(node, returnEvent, () => {
1508
+ resolve(result.value);
1509
+ });
1510
+ } else {
1511
+ Promise.resolve(result.value).then(
1512
+ (value) => step2(() => gen.next(value), true),
1513
+ (error) => step2(() => gen.throw(error), true, error)
1514
+ );
1515
+ }
1516
+ }
1517
+ function handleThrow(error) {
1518
+ const throwEventId = ++middlewareIdCounter;
1519
+ const throwEvent = {
1520
+ type: "flow_throw",
1521
+ name: actionName,
1522
+ id: throwEventId,
1523
+ parentId: spawnEvent.id,
1524
+ rootId: spawnEvent.rootId,
1525
+ context: node.getInstance(),
1526
+ tree: node.getRoot().getInstance(),
1527
+ args: [error],
1528
+ parentEvent: spawnEvent
1529
+ };
1530
+ runMiddlewares(node, throwEvent, () => {
1531
+ reject(error);
1532
+ });
1533
+ }
1534
+ step2(() => gen.next(void 0), false);
1535
+ });
1536
+ });
1537
+ };
1538
+ }
1539
+ var actionRecorders = /* @__PURE__ */ new WeakMap();
1540
+ function recordActions(target) {
1541
+ const node = getStateTreeNode(target);
1542
+ const actions = [];
1543
+ const recorder = (action) => {
1544
+ if (node.$isAlive) {
1545
+ actions.push(action);
1546
+ }
1547
+ };
1548
+ let recorders = actionRecorders.get(node);
1549
+ if (!recorders) {
1550
+ recorders = /* @__PURE__ */ new Set();
1551
+ actionRecorders.set(node, recorders);
1552
+ }
1553
+ recorders.add(recorder);
1554
+ return {
1555
+ actions,
1556
+ stop: () => {
1557
+ const currentRecorders = actionRecorders.get(node);
1558
+ if (currentRecorders) {
1559
+ currentRecorders.delete(recorder);
1560
+ if (currentRecorders.size === 0) {
1561
+ }
1562
+ }
1563
+ },
1564
+ replay: (replayTarget) => {
1565
+ const replayNode = getStateTreeNode(replayTarget);
1566
+ for (const action of actions) {
1567
+ const instance = replayNode.getInstance();
1568
+ if (typeof instance[action.name] === "function") {
1569
+ instance[action.name](...action.args);
1570
+ }
1571
+ }
1572
+ }
1573
+ };
1574
+ }
1575
+ function notifyActionRecorders(node, action) {
1576
+ let current = node;
1577
+ while (current) {
1578
+ const recorders = actionRecorders.get(current);
1579
+ if (recorders) {
1580
+ recorders.forEach((recorder) => recorder(action));
1581
+ }
1582
+ current = current.$parent;
1583
+ }
1584
+ }
1585
+ registerActionRecorderHook((node, call) => {
1586
+ const action = {
1587
+ name: call.name,
1588
+ path: call.path,
1589
+ args: call.args
1590
+ };
1591
+ notifyActionRecorders(node, action);
1592
+ });
1593
+ var unprotectedNodes = /* @__PURE__ */ new WeakSet();
1594
+ function isNodeUnprotected(node) {
1595
+ let curr = node;
1596
+ while (curr) {
1597
+ if (unprotectedNodes.has(curr)) {
1598
+ return true;
1599
+ }
1600
+ curr = curr.$parent;
1601
+ }
1602
+ return false;
1603
+ }
1604
+ function protect(target) {
1605
+ const node = getStateTreeNode(target);
1606
+ unprotectedNodes.delete(node);
1607
+ }
1608
+ function unprotect(target) {
1609
+ const node = getStateTreeNode(target);
1610
+ unprotectedNodes.add(node);
1611
+ }
1612
+ function isProtected(target) {
1613
+ const node = getStateTreeNode(target);
1614
+ return !isNodeUnprotected(node);
1615
+ }
1616
+ function canWrite(node) {
1617
+ if (!node.$isAlive) {
1618
+ return false;
1619
+ }
1620
+ if (getIsApplyingSnapshotOrPatch()) {
1621
+ return true;
1622
+ }
1623
+ if (typeof process !== "undefined" && process.env && process.env.NODE_ENV === "production") {
1624
+ return true;
1625
+ }
1626
+ if (isNodeUnprotected(node)) {
1627
+ return true;
1628
+ }
1629
+ return isActionRunning();
1630
+ }
1631
+ function applyAction(target, action) {
1632
+ const node = getStateTreeNode(target);
1633
+ let currentNode = node;
1634
+ if (action.path) {
1635
+ const parts = action.path.split("/").filter(Boolean);
1636
+ for (const part of parts) {
1637
+ const child = currentNode.getChild(part);
1638
+ if (!child) {
1639
+ throw new Error(
1640
+ `[jotai-state-tree] Invalid action path: ${action.path}`
1641
+ );
1642
+ }
1643
+ currentNode = child;
1644
+ }
1645
+ }
1646
+ const instance = currentNode.getInstance();
1647
+ if (typeof instance[action.name] !== "function") {
1648
+ throw new Error(`[jotai-state-tree] Action '${action.name}' not found`);
1649
+ }
1650
+ return instance[action.name](...action.args);
1651
+ }
1652
+ function escapeJsonPath(path) {
1653
+ return path.replace(/~/g, "~0").replace(/\//g, "~1");
1654
+ }
1655
+ function unescapeJsonPath(path) {
1656
+ return path.replace(/~1/g, "/").replace(/~0/g, "~");
1657
+ }
1658
+ function splitJsonPath(path) {
1659
+ return path.split("/").filter(Boolean).map(unescapeJsonPath);
1660
+ }
1661
+ function joinJsonPath(parts) {
1662
+ return parts.map(escapeJsonPath).join("/");
1663
+ }
1664
+ setLifecycleHookHandlers({
1665
+ runAfterAttach,
1666
+ runBeforeDetach,
1667
+ runBeforeDestroy
1668
+ });
1669
+
1670
+ // src/model.ts
1671
+ import { atom as atom2 } from "jotai";
1672
+ var MAX_CACHE_SIZE = 100;
1673
+ var LRUCache = class {
1674
+ constructor(maxSize = MAX_CACHE_SIZE) {
1675
+ this.cache = /* @__PURE__ */ new Map();
1676
+ this.maxSize = maxSize;
1677
+ }
1678
+ get(key) {
1679
+ const value = this.cache.get(key);
1680
+ if (value !== void 0) {
1681
+ this.cache.delete(key);
1682
+ this.cache.set(key, value);
1683
+ }
1684
+ return value;
1685
+ }
1686
+ set(key, value) {
1687
+ if (this.cache.has(key)) {
1688
+ this.cache.delete(key);
1689
+ } else if (this.cache.size >= this.maxSize) {
1690
+ const firstKey = this.cache.keys().next().value;
1691
+ if (firstKey !== void 0) {
1692
+ this.cache.delete(firstKey);
1693
+ }
1694
+ }
1695
+ this.cache.set(key, value);
1696
+ }
1697
+ has(key) {
1698
+ return this.cache.has(key);
1699
+ }
1700
+ clear() {
1701
+ this.cache.clear();
1702
+ }
1703
+ get size() {
1704
+ return this.cache.size;
1705
+ }
1706
+ };
1707
+ var ModelType = class _ModelType {
1708
+ constructor(config) {
1709
+ this._kind = "model";
1710
+ this.config = config;
1711
+ this.name = config.name;
1712
+ this.properties = config.properties;
1713
+ for (const [key, type] of Object.entries(config.properties)) {
1714
+ if (type._kind === "identifier" || type._kind === "identifierNumber") {
1715
+ this.identifierAttribute = key;
1716
+ break;
1717
+ }
1718
+ }
1719
+ }
1720
+ create(snapshot, env) {
1721
+ let processedSnapshot = snapshot ?? {};
1722
+ if (this.config.preProcessor) {
1723
+ processedSnapshot = this.config.preProcessor(
1724
+ processedSnapshot
1725
+ );
1726
+ }
1727
+ const node = new StateTreeNode(this, processedSnapshot, env);
1728
+ node.preProcessor = this.config.preProcessor;
1729
+ node.postProcessor = this.config.postProcessor;
1730
+ const propertyAtoms = /* @__PURE__ */ new Map();
1731
+ const store = getGlobalStore();
1732
+ for (const [key, propType] of Object.entries(this.properties)) {
1733
+ const type = propType;
1734
+ const initialValue = processedSnapshot?.[key];
1735
+ const isComplexType = type._kind === "model" || type._kind === "array" || type._kind === "map";
1736
+ if (isComplexType) {
1737
+ const childInstance = type.create(initialValue, env);
1738
+ const childNode = getStateTreeNode(childInstance);
1739
+ node.addChild(key, childNode);
1740
+ propertyAtoms.set(key, childNode.valueAtom);
1741
+ } else {
1742
+ const value = type.create(initialValue, env);
1743
+ if (value && typeof value === "object" && $treenode in value) {
1744
+ const childNode = getStateTreeNode(value);
1745
+ node.addChild(key, childNode);
1746
+ propertyAtoms.set(key, childNode.valueAtom);
1747
+ } else {
1748
+ const propAtom = atom2(value);
1749
+ propertyAtoms.set(key, propAtom);
1750
+ const childNode = new StateTreeNode(type, value, env, node, key);
1751
+ childNode.valueAtom = propAtom;
1752
+ node.addChild(key, childNode);
1753
+ }
1754
+ }
1755
+ }
1756
+ const instance = this.createInstanceProxy(node, propertyAtoms, store);
1757
+ if (this.identifierAttribute) {
1758
+ const idValue = processedSnapshot?.[this.identifierAttribute];
1759
+ if (idValue !== void 0) {
1760
+ node.registerIdentifier(this.name, idValue);
1761
+ }
1762
+ }
1763
+ node.setInstance(instance);
1764
+ if (this.config.hooks) {
1765
+ registerHooks(node, {
1766
+ afterCreate: this.config.hooks.afterCreate ? () => this.config.hooks.afterCreate(instance) : void 0,
1767
+ afterAttach: this.config.hooks.afterAttach ? () => this.config.hooks.afterAttach(instance) : void 0,
1768
+ beforeDetach: this.config.hooks.beforeDetach ? () => this.config.hooks.beforeDetach(instance) : void 0,
1769
+ beforeDestroy: this.config.hooks.beforeDestroy ? () => this.config.hooks.beforeDestroy(instance) : void 0
1770
+ });
1771
+ }
1772
+ for (const initializer of this.config.initializers) {
1773
+ initializer(instance);
1774
+ }
1775
+ runAfterCreate(node);
1776
+ return instance;
1777
+ }
1778
+ createInstanceProxy(node, propertyAtoms, store) {
1779
+ const self = this;
1780
+ const viewCache = new LRUCache(MAX_CACHE_SIZE);
1781
+ const actionCache = new LRUCache(MAX_CACHE_SIZE);
1782
+ const allViews = {};
1783
+ const allActions = {};
1784
+ const volatileState = {};
1785
+ const base = {
1786
+ [$treenode]: node
1787
+ };
1788
+ const proxy = new Proxy(base, {
1789
+ get(target, prop) {
1790
+ if (prop === $treenode) {
1791
+ return node;
1792
+ }
1793
+ if (prop === "$treenode") {
1794
+ return node;
1795
+ }
1796
+ if (!node.$isAlive) {
1797
+ throw new Error(`[jotai-state-tree] Cannot access '${String(prop)}' - the node is dead.`);
1798
+ }
1799
+ const propStr = String(prop);
1800
+ if (propertyAtoms.has(propStr)) {
1801
+ const childNode = node.getChild(propStr);
1802
+ if (childNode) {
1803
+ trackNodeAccess(childNode);
1804
+ const instance = childNode.getInstance();
1805
+ if (instance !== void 0) {
1806
+ if (instance && typeof instance === "object" && $treenode in instance) {
1807
+ return instance;
1808
+ }
1809
+ }
1810
+ return store.get(propertyAtoms.get(propStr));
1811
+ }
1812
+ }
1813
+ if (propStr in node.volatileState) {
1814
+ trackNodeAccess(node);
1815
+ return node.volatileState[propStr];
1816
+ }
1817
+ if (propStr in allViews) {
1818
+ const descriptor = allViews[propStr];
1819
+ if (descriptor.get) {
1820
+ return descriptor.get.call(proxy);
1821
+ }
1822
+ if (typeof descriptor.value === "function") {
1823
+ return descriptor.value.bind(proxy);
1824
+ }
1825
+ return descriptor.value;
1826
+ }
1827
+ if (propStr === "toggle") {
1828
+ console.log("PROXY GET toggle:", propStr, "in allActions:", propStr in allActions, "keys:", Object.keys(allActions), "node alive:", node.$isAlive);
1829
+ }
1830
+ if (propStr in allActions) {
1831
+ return allActions[propStr];
1832
+ }
1833
+ return void 0;
1834
+ },
1835
+ set(target, prop, value) {
1836
+ const propStr = String(prop);
1837
+ if (propertyAtoms.has(propStr)) {
1838
+ if (!node.$isAlive) {
1839
+ throw new Error(`[jotai-state-tree] Cannot modify '${propStr}' - the node is dead.`);
1840
+ }
1841
+ if (!canWrite(node)) {
1842
+ throw new Error(`Cannot modify '${propStr}' - the object is protected and can only be modified inside an action.`);
1843
+ }
1844
+ const propType = self.properties[propStr];
1845
+ const existingChildNode = node.getChild(propStr);
1846
+ if (propType._kind === "model") {
1847
+ if (existingChildNode) {
1848
+ applySnapshotToNode(existingChildNode, value);
1849
+ }
1850
+ return true;
1851
+ }
1852
+ if (propType._kind === "array" || propType._kind === "map") {
1853
+ if (existingChildNode) {
1854
+ existingChildNode.setValue(value);
1855
+ }
1856
+ return true;
1857
+ }
1858
+ const oldValue = existingChildNode?.getValue();
1859
+ const newValue = propType.create(value, node.$env);
1860
+ const isOldComplex = oldValue && typeof oldValue === "object" && $treenode in oldValue;
1861
+ const isNewComplex = newValue && typeof newValue === "object" && $treenode in newValue;
1862
+ if (existingChildNode && !isOldComplex && !isNewComplex) {
1863
+ existingChildNode.setValue(newValue);
1864
+ return true;
1865
+ }
1866
+ if (existingChildNode) {
1867
+ existingChildNode.destroy();
1868
+ node.getChildren().delete(propStr);
1869
+ }
1870
+ if (isNewComplex) {
1871
+ const newChildNode = getStateTreeNode(newValue);
1872
+ node.addChild(propStr, newChildNode);
1873
+ propertyAtoms.set(propStr, newChildNode.valueAtom);
1874
+ } else {
1875
+ const newChildNode = new StateTreeNode(
1876
+ propType,
1877
+ newValue,
1878
+ node.$env,
1879
+ node,
1880
+ propStr
1881
+ );
1882
+ const propAtom = atom2(newValue);
1883
+ newChildNode.valueAtom = propAtom;
1884
+ node.addChild(propStr, newChildNode);
1885
+ propertyAtoms.set(propStr, propAtom);
1886
+ store.set(propAtom, newValue);
1887
+ }
1888
+ node.notifyPropertyChange(propStr, newValue, oldValue);
1889
+ return true;
1890
+ }
1891
+ if (propStr in node.volatileState) {
1892
+ if (!node.$isAlive) {
1893
+ throw new Error(`[jotai-state-tree] Cannot modify volatile property '${propStr}' - the node is dead.`);
1894
+ }
1895
+ if (!canWrite(node)) {
1896
+ throw new Error(`Cannot modify volatile property '${propStr}' - the object is protected and can only be modified inside an action.`);
1897
+ }
1898
+ const oldValue = node.volatileState[propStr];
1899
+ if (oldValue !== value) {
1900
+ node.volatileState[propStr] = value;
1901
+ node.notifyVolatileChange();
1902
+ }
1903
+ return true;
1904
+ }
1905
+ return false;
1906
+ },
1907
+ has(target, prop) {
1908
+ const propStr = String(prop);
1909
+ return prop === $treenode || propertyAtoms.has(propStr) || propStr in allViews || propStr in allActions || propStr in node.volatileState;
1910
+ },
1911
+ ownKeys() {
1912
+ return [
1913
+ ...propertyAtoms.keys(),
1914
+ ...Object.keys(allViews),
1915
+ ...Object.keys(allActions),
1916
+ ...Object.keys(node.volatileState)
1917
+ ];
1918
+ },
1919
+ getOwnPropertyDescriptor(target, prop) {
1920
+ const propStr = String(prop);
1921
+ if (propertyAtoms.has(propStr) || propStr in allViews || propStr in allActions || propStr in node.volatileState) {
1922
+ return {
1923
+ configurable: true,
1924
+ enumerable: true,
1925
+ writable: true
1926
+ };
1927
+ }
1928
+ return void 0;
1929
+ }
1930
+ });
1931
+ for (const viewFn of this.config.views) {
1932
+ const views = viewFn(proxy);
1933
+ for (const [key, value] of Object.entries(
1934
+ Object.getOwnPropertyDescriptors(views)
1935
+ )) {
1936
+ allViews[key] = value;
1937
+ }
1938
+ }
1939
+ for (const actionFn of this.config.actions) {
1940
+ const actions = actionFn(proxy);
1941
+ for (const [key, value] of Object.entries(actions)) {
1942
+ if (typeof value === "function") {
1943
+ allActions[key] = (...args) => {
1944
+ const runner = createMiddlewareRunner(node, key, args);
1945
+ return runner((currEvent) => {
1946
+ return trackAction(node, currEvent.name, currEvent.args, () => {
1947
+ return value.apply(proxy, currEvent.args);
1948
+ });
1949
+ });
1950
+ };
1951
+ if (key === "afterCreate" || key === "afterAttach" || key === "beforeDetach" || key === "beforeDestroy") {
1952
+ registerHooks(node, {
1953
+ [key]: () => {
1954
+ return value.apply(proxy);
1955
+ }
1956
+ });
1957
+ }
1958
+ }
1959
+ }
1960
+ }
1961
+ for (const volatileFn of this.config.volatiles) {
1962
+ const volatile = volatileFn(proxy);
1963
+ Object.assign(node.volatileState, volatile);
1964
+ }
1965
+ return proxy;
1966
+ }
1967
+ is(value) {
1968
+ if (!value || typeof value !== "object") return false;
1969
+ if (!($treenode in value)) return false;
1970
+ const node = value[$treenode];
1971
+ return node.$type === this || node.$type.name === this.name;
1972
+ }
1973
+ validate(value, context) {
1974
+ const errors = [];
1975
+ if (!value || typeof value !== "object") {
1976
+ return {
1977
+ valid: false,
1978
+ errors: [
1979
+ {
1980
+ context,
1981
+ value,
1982
+ message: `Value is not an object`
1983
+ }
1984
+ ]
1985
+ };
1986
+ }
1987
+ for (const [key, propType] of Object.entries(this.properties)) {
1988
+ const propValue = value[key];
1989
+ const propContext = {
1990
+ path: context.length > 0 ? `${context[0].path}/${key}` : `/${key}`,
1991
+ type: propType,
1992
+ parent: value
1993
+ };
1994
+ const result = propType.validate(propValue, [
1995
+ ...context,
1996
+ propContext
1997
+ ]);
1998
+ if (!result.valid) {
1999
+ errors.push(...result.errors);
2000
+ }
2001
+ }
2002
+ return {
2003
+ valid: errors.length === 0,
2004
+ errors
2005
+ };
2006
+ }
2007
+ // ============================================================================
2008
+ // Model Modifiers
2009
+ // ============================================================================
2010
+ named(name) {
2011
+ return new _ModelType({
2012
+ ...this.config,
2013
+ name
2014
+ });
2015
+ }
2016
+ props(properties) {
2017
+ return new _ModelType({
2018
+ ...this.config,
2019
+ properties: { ...this.config.properties, ...properties }
2020
+ });
2021
+ }
2022
+ views(fn) {
2023
+ return new _ModelType({
2024
+ ...this.config,
2025
+ views: [
2026
+ ...this.config.views,
2027
+ fn
2028
+ ]
2029
+ });
2030
+ }
2031
+ actions(fn) {
2032
+ return new _ModelType({
2033
+ ...this.config,
2034
+ actions: [
2035
+ ...this.config.actions,
2036
+ fn
2037
+ ]
2038
+ });
2039
+ }
2040
+ volatile(fn) {
2041
+ return new _ModelType({
2042
+ ...this.config,
2043
+ volatiles: [
2044
+ ...this.config.volatiles,
2045
+ fn
2046
+ ]
2047
+ });
2048
+ }
2049
+ preProcessSnapshot(fn) {
2050
+ return new _ModelType({
2051
+ ...this.config,
2052
+ preProcessor: fn
2053
+ });
2054
+ }
2055
+ postProcessSnapshot(fn) {
2056
+ return new _ModelType({
2057
+ ...this.config,
2058
+ postProcessor: fn
2059
+ });
2060
+ }
2061
+ extend(fn) {
2062
+ const viewsFn = (self) => {
2063
+ const result = fn(self);
2064
+ return result.views ?? {};
2065
+ };
2066
+ const actionsFn = (self) => {
2067
+ const result = fn(self);
2068
+ return result.actions ?? {};
2069
+ };
2070
+ const volatileFn = (self) => {
2071
+ const result = fn(self);
2072
+ return result.state ?? {};
2073
+ };
2074
+ return new _ModelType({
2075
+ ...this.config,
2076
+ views: [
2077
+ ...this.config.views,
2078
+ viewsFn
2079
+ ],
2080
+ actions: [
2081
+ ...this.config.actions,
2082
+ actionsFn
2083
+ ],
2084
+ volatiles: [
2085
+ ...this.config.volatiles,
2086
+ volatileFn
2087
+ ]
2088
+ });
2089
+ }
2090
+ /**
2091
+ * Add afterCreate lifecycle hook
2092
+ */
2093
+ afterCreate(fn) {
2094
+ return new _ModelType({
2095
+ ...this.config,
2096
+ hooks: {
2097
+ ...this.config.hooks,
2098
+ afterCreate: fn
2099
+ }
2100
+ });
2101
+ }
2102
+ /**
2103
+ * Add afterAttach lifecycle hook
2104
+ */
2105
+ afterAttach(fn) {
2106
+ return new _ModelType({
2107
+ ...this.config,
2108
+ hooks: {
2109
+ ...this.config.hooks,
2110
+ afterAttach: fn
2111
+ }
2112
+ });
2113
+ }
2114
+ /**
2115
+ * Add beforeDetach lifecycle hook
2116
+ */
2117
+ beforeDetach(fn) {
2118
+ return new _ModelType({
2119
+ ...this.config,
2120
+ hooks: {
2121
+ ...this.config.hooks,
2122
+ beforeDetach: fn
2123
+ }
2124
+ });
2125
+ }
2126
+ /**
2127
+ * Add beforeDestroy lifecycle hook
2128
+ */
2129
+ beforeDestroy(fn) {
2130
+ return new _ModelType({
2131
+ ...this.config,
2132
+ hooks: {
2133
+ ...this.config.hooks,
2134
+ beforeDestroy: fn
2135
+ }
2136
+ });
2137
+ }
2138
+ /**
2139
+ * Apply a mixin to this model.
2140
+ * The mixin's views, actions, and volatile state are added to the model.
2141
+ */
2142
+ apply(mixinDef) {
2143
+ const newViews = [];
2144
+ const newActions = [];
2145
+ const newVolatiles = [];
2146
+ if (mixinDef.views) {
2147
+ const viewsFn = mixinDef.views;
2148
+ newViews.push(
2149
+ (self) => viewsFn(self)
2150
+ );
2151
+ }
2152
+ if (mixinDef.actions) {
2153
+ const actionsFn = mixinDef.actions;
2154
+ newActions.push(
2155
+ (self) => actionsFn(self)
2156
+ );
2157
+ }
2158
+ if (mixinDef.volatile) {
2159
+ const volatileFn = mixinDef.volatile;
2160
+ newVolatiles.push(
2161
+ (self) => volatileFn(
2162
+ self
2163
+ )
2164
+ );
2165
+ }
2166
+ return new _ModelType({
2167
+ ...this.config,
2168
+ views: [...this.config.views, ...newViews],
2169
+ actions: [...this.config.actions, ...newActions],
2170
+ volatiles: [...this.config.volatiles, ...newVolatiles]
2171
+ });
2172
+ }
2173
+ /**
2174
+ * Get the internal config for use by compose
2175
+ * @internal
2176
+ */
2177
+ getConfig() {
2178
+ return this.config;
2179
+ }
2180
+ };
2181
+ function model(nameOrProperties, maybeProperties) {
2182
+ const name = typeof nameOrProperties === "string" ? nameOrProperties : "AnonymousModel";
2183
+ const properties = typeof nameOrProperties === "string" ? maybeProperties : nameOrProperties;
2184
+ return new ModelType({
2185
+ name,
2186
+ properties,
2187
+ views: [],
2188
+ actions: [],
2189
+ volatiles: [],
2190
+ initializers: [],
2191
+ hooks: {}
2192
+ });
2193
+ }
2194
+ function compose(...args) {
2195
+ const name = typeof args[0] === "string" ? args[0] : "ComposedModel";
2196
+ const types = typeof args[0] === "string" ? args.slice(1) : args;
2197
+ const mergedProperties = {};
2198
+ const mergedViews = [];
2199
+ const mergedActions = [];
2200
+ const mergedVolatiles = [];
2201
+ const mergedInitializers = [];
2202
+ let mergedHooks = {};
2203
+ for (const type of types) {
2204
+ Object.assign(mergedProperties, type.properties);
2205
+ if (type instanceof ModelType) {
2206
+ const config = type.getConfig();
2207
+ mergedViews.push(...config.views);
2208
+ mergedActions.push(...config.actions);
2209
+ mergedVolatiles.push(...config.volatiles);
2210
+ mergedInitializers.push(...config.initializers);
2211
+ mergedHooks = { ...mergedHooks, ...config.hooks };
2212
+ }
2213
+ }
2214
+ return new ModelType({
2215
+ name,
2216
+ properties: mergedProperties,
2217
+ views: mergedViews,
2218
+ actions: mergedActions,
2219
+ volatiles: mergedVolatiles,
2220
+ initializers: mergedInitializers,
2221
+ hooks: mergedHooks
2222
+ });
2223
+ }
2224
+ function mixin(config) {
2225
+ return {
2226
+ _kind: "mixin",
2227
+ requires: config.requires ?? {},
2228
+ views: config.views,
2229
+ actions: config.actions,
2230
+ volatile: config.volatile,
2231
+ // Phantom types for inference
2232
+ _V: void 0,
2233
+ _A: void 0,
2234
+ _Vol: void 0
2235
+ };
2236
+ }
2237
+
2238
+ // src/array.ts
2239
+ var MSTArray = class _MSTArray extends Array {
2240
+ constructor(node, itemType, items = []) {
2241
+ super(...items);
2242
+ this._isMutating = false;
2243
+ this.node = node;
2244
+ this.itemType = itemType;
2245
+ this._isMutating = false;
2246
+ Object.setPrototypeOf(this, _MSTArray.prototype);
2247
+ }
2248
+ checkWrite() {
2249
+ if (!this.node.$isAlive) {
2250
+ throw new Error("[jotai-state-tree] Cannot modify array - the node is dead.");
2251
+ }
2252
+ if (!canWrite(this.node)) {
2253
+ throw new Error(
2254
+ `Cannot modify the array - the parent object is protected and can only be modified inside an action.`
2255
+ );
2256
+ }
2257
+ }
2258
+ replace(items) {
2259
+ this.checkWrite();
2260
+ const wasMutating = this._isMutating;
2261
+ this._isMutating = true;
2262
+ try {
2263
+ this.length = 0;
2264
+ this.push(...items);
2265
+ this.syncToNode();
2266
+ } finally {
2267
+ this._isMutating = wasMutating;
2268
+ }
2269
+ }
2270
+ clear() {
2271
+ this.checkWrite();
2272
+ const wasMutating = this._isMutating;
2273
+ this._isMutating = true;
2274
+ try {
2275
+ this.length = 0;
2276
+ this.syncToNode();
2277
+ } finally {
2278
+ this._isMutating = wasMutating;
2279
+ }
2280
+ }
2281
+ remove(item) {
2282
+ this.checkWrite();
2283
+ const wasMutating = this._isMutating;
2284
+ this._isMutating = true;
2285
+ try {
2286
+ const index = this.indexOf(item);
2287
+ if (index >= 0) {
2288
+ this.splice(index, 1);
2289
+ this.syncToNode();
2290
+ return true;
2291
+ }
2292
+ return false;
2293
+ } finally {
2294
+ this._isMutating = wasMutating;
2295
+ }
2296
+ }
2297
+ spliceWithArray(index, deleteCount, newItems) {
2298
+ this.checkWrite();
2299
+ const wasMutating = this._isMutating;
2300
+ this._isMutating = true;
2301
+ try {
2302
+ const result = deleteCount !== void 0 ? newItems ? this.splice(index, deleteCount, ...newItems) : this.splice(index, deleteCount) : this.splice(index);
2303
+ this.syncToNode();
2304
+ return result;
2305
+ } finally {
2306
+ this._isMutating = wasMutating;
2307
+ }
2308
+ }
2309
+ // Override mutating methods to sync
2310
+ push(...items) {
2311
+ this.checkWrite();
2312
+ const wasMutating = this._isMutating;
2313
+ this._isMutating = true;
2314
+ try {
2315
+ const result = super.push(...items);
2316
+ this.syncToNode();
2317
+ return result;
2318
+ } finally {
2319
+ this._isMutating = wasMutating;
2320
+ }
2321
+ }
2322
+ pop() {
2323
+ this.checkWrite();
2324
+ const wasMutating = this._isMutating;
2325
+ this._isMutating = true;
2326
+ try {
2327
+ const result = super.pop();
2328
+ this.syncToNode();
2329
+ return result;
2330
+ } finally {
2331
+ this._isMutating = wasMutating;
2332
+ }
2333
+ }
2334
+ shift() {
2335
+ this.checkWrite();
2336
+ const wasMutating = this._isMutating;
2337
+ this._isMutating = true;
2338
+ try {
2339
+ const result = super.shift();
2340
+ this.syncToNode();
2341
+ return result;
2342
+ } finally {
2343
+ this._isMutating = wasMutating;
2344
+ }
2345
+ }
2346
+ unshift(...items) {
2347
+ this.checkWrite();
2348
+ const wasMutating = this._isMutating;
2349
+ this._isMutating = true;
2350
+ try {
2351
+ const result = super.unshift(...items);
2352
+ this.syncToNode();
2353
+ return result;
2354
+ } finally {
2355
+ this._isMutating = wasMutating;
2356
+ }
2357
+ }
2358
+ splice(start, deleteCount, ...items) {
2359
+ this.checkWrite();
2360
+ const wasMutating = this._isMutating;
2361
+ this._isMutating = true;
2362
+ try {
2363
+ const result = deleteCount !== void 0 ? super.splice(start, deleteCount, ...items) : super.splice(start);
2364
+ this.syncToNode();
2365
+ return result;
2366
+ } finally {
2367
+ this._isMutating = wasMutating;
2368
+ }
2369
+ }
2370
+ sort(compareFn) {
2371
+ this.checkWrite();
2372
+ const wasMutating = this._isMutating;
2373
+ this._isMutating = true;
2374
+ try {
2375
+ super.sort(compareFn);
2376
+ this.syncToNode();
2377
+ return this;
2378
+ } finally {
2379
+ this._isMutating = wasMutating;
2380
+ }
2381
+ }
2382
+ reverse() {
2383
+ this.checkWrite();
2384
+ const wasMutating = this._isMutating;
2385
+ this._isMutating = true;
2386
+ try {
2387
+ super.reverse();
2388
+ this.syncToNode();
2389
+ return this;
2390
+ } finally {
2391
+ this._isMutating = wasMutating;
2392
+ }
2393
+ }
2394
+ fill(value, start, end) {
2395
+ this.checkWrite();
2396
+ const wasMutating = this._isMutating;
2397
+ this._isMutating = true;
2398
+ try {
2399
+ super.fill(value, start, end);
2400
+ this.syncToNode();
2401
+ return this;
2402
+ } finally {
2403
+ this._isMutating = wasMutating;
2404
+ }
2405
+ }
2406
+ copyWithin(target, start, end) {
2407
+ this.checkWrite();
2408
+ const wasMutating = this._isMutating;
2409
+ this._isMutating = true;
2410
+ try {
2411
+ super.copyWithin(target, start, end);
2412
+ this.syncToNode();
2413
+ return this;
2414
+ } finally {
2415
+ this._isMutating = wasMutating;
2416
+ }
2417
+ }
2418
+ toJSON() {
2419
+ return [...this];
2420
+ }
2421
+ syncToNode() {
2422
+ const oldArray = this.node.getValue() || [];
2423
+ const newArray = [...this];
2424
+ const oldSnapshots = /* @__PURE__ */ new Map();
2425
+ for (let i = 0; i < oldArray.length; i++) {
2426
+ const childNode = this.node.getChild(String(i));
2427
+ oldSnapshots.set(i, childNode ? getSnapshotFromNode(childNode) : oldArray[i]);
2428
+ }
2429
+ const existingChildNodes = /* @__PURE__ */ new Set();
2430
+ for (const [, child] of this.node.getChildren()) {
2431
+ existingChildNodes.add(child);
2432
+ }
2433
+ const newChildren = /* @__PURE__ */ new Map();
2434
+ const keptNodes = /* @__PURE__ */ new Set();
2435
+ this.forEach((item, index) => {
2436
+ const key = String(index);
2437
+ if (item && typeof item === "object" && $treenode in item) {
2438
+ const childNode = getStateTreeNode(item);
2439
+ newChildren.set(key, childNode);
2440
+ keptNodes.add(childNode);
2441
+ } else {
2442
+ let reusedNode = null;
2443
+ const resolveActualType = (type) => {
2444
+ let current = type;
2445
+ while (current) {
2446
+ if (current._kind === "optional" || current._kind === "maybe" || current._kind === "maybeNull" || current._kind === "refinement") {
2447
+ current = current._subType;
2448
+ } else if (current._kind === "late") {
2449
+ current = current._definition();
2450
+ } else {
2451
+ break;
2452
+ }
2453
+ }
2454
+ return current;
2455
+ };
2456
+ const actualType = resolveActualType(this.itemType);
2457
+ const isComplex = actualType._kind === "model" || actualType._kind === "array" || actualType._kind === "map";
2458
+ const identifierAttr = actualType.identifierAttribute;
2459
+ if (identifierAttr && item && typeof item === "object") {
2460
+ const idValue = item[identifierAttr];
2461
+ if (idValue !== void 0 && idValue !== null) {
2462
+ for (const existingNode of existingChildNodes) {
2463
+ if (!keptNodes.has(existingNode) && existingNode.identifierValue === idValue) {
2464
+ reusedNode = existingNode;
2465
+ break;
2466
+ }
2467
+ }
2468
+ }
2469
+ }
2470
+ if (!reusedNode && !identifierAttr) {
2471
+ const existingNodeAtIndex = this.node.getChild(key);
2472
+ if (existingNodeAtIndex && !keptNodes.has(existingNodeAtIndex) && existingNodeAtIndex.$type === this.itemType) {
2473
+ reusedNode = existingNodeAtIndex;
2474
+ }
2475
+ }
2476
+ if (reusedNode) {
2477
+ applySnapshotToNode(reusedNode, item);
2478
+ const instance = isComplex ? reusedNode.getInstance() : reusedNode.getValue();
2479
+ newChildren.set(key, reusedNode);
2480
+ keptNodes.add(reusedNode);
2481
+ this[index] = instance;
2482
+ newArray[index] = instance;
2483
+ } else {
2484
+ const instance = this.itemType.create(item);
2485
+ if (instance && typeof instance === "object" && $treenode in instance) {
2486
+ const childNode = getStateTreeNode(instance);
2487
+ newChildren.set(key, childNode);
2488
+ keptNodes.add(childNode);
2489
+ this[index] = instance;
2490
+ newArray[index] = instance;
2491
+ } else {
2492
+ let reusedPrimitiveNode = null;
2493
+ for (const existingNode of existingChildNodes) {
2494
+ if (!keptNodes.has(existingNode) && existingNode.getValue() === item) {
2495
+ reusedPrimitiveNode = existingNode;
2496
+ break;
2497
+ }
2498
+ }
2499
+ if (reusedPrimitiveNode) {
2500
+ newChildren.set(key, reusedPrimitiveNode);
2501
+ keptNodes.add(reusedPrimitiveNode);
2502
+ this[index] = instance;
2503
+ newArray[index] = instance;
2504
+ } else {
2505
+ const childNode = new StateTreeNode(
2506
+ this.itemType,
2507
+ item,
2508
+ this.node.$env
2509
+ );
2510
+ newChildren.set(key, childNode);
2511
+ keptNodes.add(childNode);
2512
+ this[index] = instance;
2513
+ newArray[index] = instance;
2514
+ }
2515
+ }
2516
+ }
2517
+ }
2518
+ });
2519
+ for (const existingNode of existingChildNodes) {
2520
+ if (!keptNodes.has(existingNode)) {
2521
+ existingNode.destroy();
2522
+ }
2523
+ }
2524
+ this.node.getChildren().clear();
2525
+ for (const [key, childNode] of newChildren) {
2526
+ this.node.addChild(key, childNode);
2527
+ }
2528
+ const patches = [];
2529
+ const reversePatches = [];
2530
+ if (newArray.length > oldArray.length && oldArray.every((val, idx) => val === newArray[idx])) {
2531
+ for (let i = oldArray.length; i < newArray.length; i++) {
2532
+ const childNode = this.node.getChild(String(i));
2533
+ const valSnap = childNode ? getSnapshotFromNode(childNode) : newArray[i];
2534
+ patches.push({
2535
+ op: "add",
2536
+ path: `${this.node.$path}/${i}`,
2537
+ value: valSnap
2538
+ });
2539
+ reversePatches.push({
2540
+ op: "remove",
2541
+ path: `${this.node.$path}/${i}`
2542
+ });
2543
+ }
2544
+ } else if (newArray.length < oldArray.length && newArray.every((val, idx) => val === oldArray[idx])) {
2545
+ for (let i = oldArray.length - 1; i >= newArray.length; i--) {
2546
+ const oldValSnap = oldSnapshots.get(i);
2547
+ patches.push({
2548
+ op: "remove",
2549
+ path: `${this.node.$path}/${i}`
2550
+ });
2551
+ reversePatches.push({
2552
+ op: "add",
2553
+ path: `${this.node.$path}/${i}`,
2554
+ value: oldValSnap
2555
+ });
2556
+ }
2557
+ } else {
2558
+ const oldSnap = oldArray.map((_, idx) => oldSnapshots.get(idx));
2559
+ const newSnap = newArray.map((_, idx) => {
2560
+ const childNode = this.node.getChild(String(idx));
2561
+ return childNode ? getSnapshotFromNode(childNode) : newArray[idx];
2562
+ });
2563
+ patches.push({
2564
+ op: "replace",
2565
+ path: this.node.$path,
2566
+ value: newSnap
2567
+ });
2568
+ reversePatches.push({
2569
+ op: "replace",
2570
+ path: this.node.$path,
2571
+ value: oldSnap
2572
+ });
2573
+ }
2574
+ const store = getGlobalStore();
2575
+ store.set(this.node.valueAtom, newArray);
2576
+ this.node.notifySnapshotChange();
2577
+ patches.forEach((patch, idx) => {
2578
+ this.node.notifyPatch(patch, reversePatches[idx]);
2579
+ });
2580
+ this.node.notifyVolatileChange();
2581
+ }
2582
+ };
2583
+ var ArrayType = class {
2584
+ constructor(itemType) {
2585
+ this._kind = "array";
2586
+ this._subType = itemType;
2587
+ this.name = `array<${itemType.name}>`;
2588
+ }
2589
+ create(snapshot, env) {
2590
+ const items = snapshot ?? [];
2591
+ const node = new StateTreeNode(this, items, env);
2592
+ const instances = items.map((item, index) => {
2593
+ const instance = this._subType.create(item, env);
2594
+ if (instance && typeof instance === "object" && $treenode in instance) {
2595
+ const childNode = getStateTreeNode(instance);
2596
+ node.addChild(String(index), childNode);
2597
+ } else {
2598
+ const childNode = new StateTreeNode(
2599
+ this._subType,
2600
+ instance,
2601
+ env,
2602
+ node,
2603
+ String(index)
2604
+ );
2605
+ node.addChild(String(index), childNode);
2606
+ }
2607
+ return instance;
2608
+ });
2609
+ const mstArray = new MSTArray(node, this._subType, instances);
2610
+ Object.defineProperty(mstArray, $treenode, {
2611
+ value: node,
2612
+ writable: false,
2613
+ enumerable: false
2614
+ });
2615
+ const proxy = new Proxy(mstArray, {
2616
+ get(target, prop, receiver) {
2617
+ if (prop === $treenode) {
2618
+ return node;
2619
+ }
2620
+ if (!node.$isAlive) {
2621
+ throw new Error("[jotai-state-tree] Cannot access array - the node is dead.");
2622
+ }
2623
+ return Reflect.get(target, prop, receiver);
2624
+ },
2625
+ set(target, prop, value) {
2626
+ const propStr = String(prop);
2627
+ const isIndex = /^\d+$/.test(propStr);
2628
+ if (isIndex || propStr === "length") {
2629
+ if (!node.$isAlive) {
2630
+ throw new Error("[jotai-state-tree] Cannot modify array - the node is dead.");
2631
+ }
2632
+ if (!canWrite(node)) {
2633
+ throw new Error(
2634
+ `Cannot modify the array - the parent object is protected and can only be modified inside an action.`
2635
+ );
2636
+ }
2637
+ target[prop] = value;
2638
+ if (!target._isMutating) {
2639
+ target.syncToNode();
2640
+ }
2641
+ return true;
2642
+ }
2643
+ target[prop] = value;
2644
+ return true;
2645
+ }
2646
+ });
2647
+ node.setInstance(proxy);
2648
+ node.setValue(instances);
2649
+ return proxy;
2650
+ }
2651
+ is(value) {
2652
+ if (!Array.isArray(value)) return false;
2653
+ return $treenode in value;
2654
+ }
2655
+ validate(value, context) {
2656
+ const errors = [];
2657
+ if (!Array.isArray(value)) {
2658
+ return {
2659
+ valid: false,
2660
+ errors: [
2661
+ {
2662
+ context,
2663
+ value,
2664
+ message: "Value is not an array"
2665
+ }
2666
+ ]
2667
+ };
2668
+ }
2669
+ value.forEach((item, index) => {
2670
+ const itemContext = {
2671
+ path: context.length > 0 ? `${context[0].path}/${index}` : `/${index}`,
2672
+ type: this._subType,
2673
+ parent: value
2674
+ };
2675
+ const result = this._subType.validate(item, [...context, itemContext]);
2676
+ if (!result.valid) {
2677
+ errors.push(...result.errors);
2678
+ }
2679
+ });
2680
+ return {
2681
+ valid: errors.length === 0,
2682
+ errors
2683
+ };
2684
+ }
2685
+ };
2686
+ function array(itemType) {
2687
+ return new ArrayType(itemType);
2688
+ }
2689
+
2690
+ // src/utilities.ts
2691
+ var OptionalType = class {
2692
+ constructor(subType, defaultValue) {
2693
+ this._kind = "optional";
2694
+ this._subType = subType;
2695
+ this._defaultValue = defaultValue;
2696
+ this.name = `optional<${subType.name}>`;
2697
+ }
2698
+ create(snapshot, env) {
2699
+ if (snapshot === void 0) {
2700
+ const defaultVal = typeof this._defaultValue === "function" ? this._defaultValue() : this._defaultValue;
2701
+ return this._subType.create(defaultVal, env);
2702
+ }
2703
+ return this._subType.create(snapshot, env);
2704
+ }
2705
+ is(value) {
2706
+ return value === void 0 || this._subType.is(value);
2707
+ }
2708
+ validate(value, context) {
2709
+ if (value === void 0) {
2710
+ return { valid: true, errors: [] };
2711
+ }
2712
+ return this._subType.validate(value, context);
2713
+ }
2714
+ };
2715
+ function optional(type, defaultValue) {
2716
+ return new OptionalType(type, defaultValue);
2717
+ }
2718
+ var MaybeType = class {
2719
+ constructor(subType) {
2720
+ this._kind = "maybe";
2721
+ this._subType = subType;
2722
+ this.name = `maybe<${subType.name}>`;
2723
+ }
2724
+ create(snapshot, env) {
2725
+ if (snapshot === void 0) {
2726
+ return void 0;
2727
+ }
2728
+ return this._subType.create(snapshot, env);
2729
+ }
2730
+ is(value) {
2731
+ return value === void 0 || this._subType.is(value);
2732
+ }
2733
+ validate(value, context) {
2734
+ if (value === void 0) {
2735
+ return { valid: true, errors: [] };
2736
+ }
2737
+ return this._subType.validate(value, context);
2738
+ }
2739
+ };
2740
+ function maybe(type) {
2741
+ return new MaybeType(type);
2742
+ }
2743
+ var MaybeNullType = class {
2744
+ constructor(subType) {
2745
+ this._kind = "maybeNull";
2746
+ this._subType = subType;
2747
+ this.name = `maybeNull<${subType.name}>`;
2748
+ }
2749
+ create(snapshot, env) {
2750
+ if (snapshot === null || snapshot === void 0) {
2751
+ return null;
2752
+ }
2753
+ return this._subType.create(snapshot, env);
2754
+ }
2755
+ is(value) {
2756
+ return value === null || this._subType.is(value);
2757
+ }
2758
+ validate(value, context) {
2759
+ if (value === null) {
2760
+ return { valid: true, errors: [] };
2761
+ }
2762
+ return this._subType.validate(value, context);
2763
+ }
2764
+ };
2765
+ function maybeNull(type) {
2766
+ return new MaybeNullType(type);
2767
+ }
2768
+ var UnionType = class {
2769
+ constructor(types, options) {
2770
+ this._kind = "union";
2771
+ this._types = types;
2772
+ this.dispatcher = options?.dispatcher;
2773
+ this.eager = options?.eager ?? true;
2774
+ this.name = `union(${types.map((t) => t.name).join(" | ")})`;
2775
+ }
2776
+ create(snapshot, env) {
2777
+ if (this.dispatcher && snapshot !== void 0) {
2778
+ const type = this.dispatcher(snapshot);
2779
+ return type.create(snapshot, env);
2780
+ }
2781
+ for (const type of this._types) {
2782
+ try {
2783
+ const result = type.validate(snapshot, []);
2784
+ if (result.valid) {
2785
+ return type.create(snapshot, env);
2786
+ }
2787
+ } catch {
2788
+ }
2789
+ }
2790
+ throw new Error(
2791
+ `[jotai-state-tree] No type in union matched the value: ${JSON.stringify(snapshot)}`
2792
+ );
2793
+ }
2794
+ is(value) {
2795
+ return this._types.some((type) => type.is(value));
2796
+ }
2797
+ validate(value, context) {
2798
+ for (const type of this._types) {
2799
+ const result = type.validate(value, context);
2800
+ if (result.valid) {
2801
+ return result;
2802
+ }
2803
+ }
2804
+ return {
2805
+ valid: false,
2806
+ errors: [
2807
+ {
2808
+ context,
2809
+ value,
2810
+ message: `Value does not match any type in union`
2811
+ }
2812
+ ]
2813
+ };
2814
+ }
2815
+ };
2816
+ function union(optionsOrType, ...rest) {
2817
+ if (optionsOrType && typeof optionsOrType === "object" && "dispatcher" in optionsOrType) {
2818
+ return new UnionType(rest, optionsOrType);
2819
+ }
2820
+ return new UnionType([optionsOrType, ...rest]);
2821
+ }
2822
+ var LateType = class {
2823
+ constructor(definition, name) {
2824
+ this._kind = "late";
2825
+ this._definition = definition;
2826
+ this.name = name ?? "late(...)";
2827
+ }
2828
+ getType() {
2829
+ if (!this.resolvedType) {
2830
+ this.resolvedType = this._definition();
2831
+ }
2832
+ return this.resolvedType;
2833
+ }
2834
+ create(snapshot, env) {
2835
+ return this.getType().create(snapshot, env);
2836
+ }
2837
+ is(value) {
2838
+ return this.getType().is(value);
2839
+ }
2840
+ validate(value, context) {
2841
+ return this.getType().validate(value, context);
2842
+ }
2843
+ };
2844
+ function late(nameOrDefinition, maybeDefinition) {
2845
+ if (typeof nameOrDefinition === "string") {
2846
+ return new LateType(maybeDefinition, nameOrDefinition);
2847
+ }
2848
+ return new LateType(nameOrDefinition);
2849
+ }
2850
+ var RefinementType = class {
2851
+ constructor(subType, predicate, message) {
2852
+ this._kind = "refinement";
2853
+ this._subType = subType;
2854
+ this._predicate = predicate;
2855
+ this.message = message ?? "Value failed refinement predicate";
2856
+ this.name = `refinement<${subType.name}>`;
2857
+ }
2858
+ create(snapshot, env) {
2859
+ const instance = this._subType.create(snapshot, env);
2860
+ if (!this._predicate(instance)) {
2861
+ const msg = typeof this.message === "function" ? this.message(instance) : this.message;
2862
+ throw new Error(`[jotai-state-tree] ${msg}`);
2863
+ }
2864
+ return instance;
2865
+ }
2866
+ is(value) {
2867
+ return this._subType.is(value) && this._predicate(value);
2868
+ }
2869
+ validate(value, context) {
2870
+ const baseResult = this._subType.validate(value, context);
2871
+ if (!baseResult.valid) {
2872
+ return baseResult;
2873
+ }
2874
+ if (!this._predicate(value)) {
2875
+ const msg = typeof this.message === "function" ? this.message(value) : this.message;
2876
+ return {
2877
+ valid: false,
2878
+ errors: [
2879
+ {
2880
+ context,
2881
+ value,
2882
+ message: msg
2883
+ }
2884
+ ]
2885
+ };
2886
+ }
2887
+ return { valid: true, errors: [] };
2888
+ }
2889
+ };
2890
+ function refinement(type, predicate, message) {
2891
+ return new RefinementType(type, predicate, message);
2892
+ }
2893
+ var ReferenceType = class {
2894
+ constructor(targetType, options) {
2895
+ this._kind = "reference";
2896
+ this._targetType = targetType;
2897
+ this.options = options;
2898
+ this.name = `reference<${targetType.name}>`;
2899
+ }
2900
+ create(snapshot, env) {
2901
+ if (snapshot === void 0) {
2902
+ throw new Error("[jotai-state-tree] Reference requires an identifier");
2903
+ }
2904
+ const self = this;
2905
+ let resolved = null;
2906
+ if (this.options?.get) {
2907
+ const result = this.options.get(snapshot, null);
2908
+ if (result) return result;
2909
+ }
2910
+ const node = new StateTreeNode(this, snapshot, env);
2911
+ node.identifierValue = snapshot;
2912
+ const proxy = new Proxy({}, {
2913
+ get(target, prop) {
2914
+ if (!resolved) {
2915
+ const targetNode = resolveIdentifier(self._targetType.name, snapshot);
2916
+ if (!targetNode) {
2917
+ throw new Error(
2918
+ `[jotai-state-tree] Failed to resolve reference '${snapshot}' to type '${self._targetType.name}'`
2919
+ );
2920
+ }
2921
+ resolved = targetNode.getInstance();
2922
+ }
2923
+ if (prop === $treenode) {
2924
+ return node;
2925
+ }
2926
+ return resolved[prop];
2927
+ },
2928
+ set(target, prop, value) {
2929
+ if (!resolved) {
2930
+ const targetNode = resolveIdentifier(self._targetType.name, snapshot);
2931
+ if (!targetNode) {
2932
+ throw new Error(
2933
+ `[jotai-state-tree] Failed to resolve reference '${snapshot}' to type '${self._targetType.name}'`
2934
+ );
2935
+ }
2936
+ resolved = targetNode.getInstance();
2937
+ }
2938
+ resolved[prop] = value;
2939
+ return true;
2940
+ },
2941
+ has(target, prop) {
2942
+ if (!resolved) {
2943
+ const targetNode = resolveIdentifier(self._targetType.name, snapshot);
2944
+ if (targetNode) {
2945
+ resolved = targetNode.getInstance();
2946
+ }
2947
+ }
2948
+ return resolved ? prop in resolved : false;
2949
+ }
2950
+ });
2951
+ node.setInstance(proxy);
2952
+ return proxy;
2953
+ }
2954
+ is(value) {
2955
+ return this._targetType.is(value);
2956
+ }
2957
+ validate(value, context) {
2958
+ if (typeof value === "string" || typeof value === "number") {
2959
+ return { valid: true, errors: [] };
2960
+ }
2961
+ return {
2962
+ valid: false,
2963
+ errors: [
2964
+ {
2965
+ context,
2966
+ value,
2967
+ message: "Reference must be a string or number identifier"
2968
+ }
2969
+ ]
2970
+ };
2971
+ }
2972
+ };
2973
+ function reference(targetType, options) {
2974
+ return new ReferenceType(targetType, options);
2975
+ }
2976
+ var SafeReferenceType = class {
2977
+ constructor(targetType, options) {
2978
+ this._kind = "safeReference";
2979
+ this._targetType = targetType;
2980
+ this.options = options;
2981
+ this.name = `safeReference<${targetType.name}>`;
2982
+ }
2983
+ create(snapshot, env) {
2984
+ if (snapshot === void 0) {
2985
+ return void 0;
2986
+ }
2987
+ const targetNode = resolveIdentifier(this._targetType.name, snapshot);
2988
+ if (!targetNode) {
2989
+ if (this.options?.onInvalidated) {
2990
+ return void 0;
2991
+ }
2992
+ return void 0;
2993
+ }
2994
+ return targetNode.getInstance();
2995
+ }
2996
+ is(value) {
2997
+ return value === void 0 || this._targetType.is(value);
2998
+ }
2999
+ validate(value, context) {
3000
+ if (value === void 0) {
3001
+ return { valid: true, errors: [] };
3002
+ }
3003
+ if (typeof value === "string" || typeof value === "number") {
3004
+ return { valid: true, errors: [] };
3005
+ }
3006
+ return {
3007
+ valid: false,
3008
+ errors: [
3009
+ {
3010
+ context,
3011
+ value,
3012
+ message: "Safe reference must be a string, number, or undefined"
3013
+ }
3014
+ ]
3015
+ };
3016
+ }
3017
+ };
3018
+ function safeReference(targetType, options) {
3019
+ return new SafeReferenceType(targetType, options);
3020
+ }
3021
+ function snapshotProcessor(type, processors) {
3022
+ return {
3023
+ name: `snapshotProcessor<${type.name}>`,
3024
+ _kind: "simple",
3025
+ _C: void 0,
3026
+ _S: void 0,
3027
+ _T: void 0,
3028
+ create(snapshot, env) {
3029
+ const processed = processors.preProcessor ? processors.preProcessor(snapshot) : snapshot;
3030
+ return type.create(processed, env);
3031
+ },
3032
+ is(value) {
3033
+ return type.is(value);
3034
+ },
3035
+ validate(value, context) {
3036
+ const processed = processors.preProcessor ? processors.preProcessor(value) : value;
3037
+ return type.validate(processed, context);
3038
+ }
3039
+ };
3040
+ }
3041
+
3042
+ // src/undo.ts
3043
+ import { atom as atom3 } from "jotai";
3044
+ var historyTrackersRegistry = /* @__PURE__ */ new WeakMap();
3045
+ var HistoryTracker = class {
3046
+ constructor(target, options = {}) {
3047
+ // Transient state
3048
+ this.autoRecord = false;
3049
+ this.isApplyingHistory = false;
3050
+ this.skipRecording = false;
3051
+ this.grouping = false;
3052
+ this.actionGrouping = false;
3053
+ this.currentGroup = [];
3054
+ this.currentGroupInverse = [];
3055
+ this.lastChangeTime = 0;
3056
+ this.disposer = null;
3057
+ this.actionDisposer = null;
3058
+ this.target = target;
3059
+ this.maxHistoryLength = options.maxHistoryLength ?? options.maxSnapshots ?? 100;
3060
+ this.groupByTime = options.groupByTime ?? false;
3061
+ this.groupingWindow = options.groupingWindow ?? 200;
3062
+ this.autoRecord = options.autoRecord ?? false;
3063
+ const initialSnapshot = getSnapshot(target);
3064
+ this.historyAtom = atom3({
3065
+ entries: [],
3066
+ currentIndex: -1,
3067
+ initialSnapshot
3068
+ });
3069
+ this.disposer = onPatch(target, (patch, reversePatch) => {
3070
+ this.recordPatch(patch, reversePatch);
3071
+ });
3072
+ this.actionDisposer = onAction(target, () => {
3073
+ const current = getCurrentAction();
3074
+ if (current && !current.parent) {
3075
+ if (this.actionGrouping) {
3076
+ this.endGroup();
3077
+ }
3078
+ }
3079
+ });
3080
+ }
3081
+ recordPatch(patch, reversePatch) {
3082
+ if (!this.autoRecord) {
3083
+ return;
3084
+ }
3085
+ if (this.isApplyingHistory || this.skipRecording) {
3086
+ return;
3087
+ }
3088
+ const node = getStateTreeNode(this.target);
3089
+ if (node.getRoot().$isApplyingHistory) {
3090
+ return;
3091
+ }
3092
+ const store = getGlobalStore();
3093
+ const now = Date.now();
3094
+ if (isActionRunning() && !this.grouping) {
3095
+ this.grouping = true;
3096
+ this.actionGrouping = true;
3097
+ this.currentGroup = [];
3098
+ this.currentGroupInverse = [];
3099
+ Promise.resolve().then(() => {
3100
+ if (this.actionGrouping) {
3101
+ this.endGroup();
3102
+ }
3103
+ });
3104
+ }
3105
+ if (this.grouping) {
3106
+ this.currentGroup.push(reversePatch);
3107
+ this.currentGroupInverse.push({ ...patch });
3108
+ return;
3109
+ }
3110
+ store.set(this.historyAtom, (prev) => {
3111
+ let entries = prev.currentIndex < prev.entries.length - 1 ? prev.entries.slice(0, prev.currentIndex + 1) : [...prev.entries];
3112
+ if (this.groupByTime && entries.length > 0 && now - this.lastChangeTime < this.groupingWindow && prev.currentIndex === prev.entries.length - 1) {
3113
+ const lastEntry = { ...entries[entries.length - 1] };
3114
+ lastEntry.patches = [...lastEntry.patches, reversePatch];
3115
+ lastEntry.inversePatches = [...lastEntry.inversePatches, { ...patch }];
3116
+ lastEntry.timestamp = now;
3117
+ lastEntry.snapshot = getSnapshot(this.target);
3118
+ entries[entries.length - 1] = lastEntry;
3119
+ this.lastChangeTime = now;
3120
+ return {
3121
+ ...prev,
3122
+ entries
3123
+ };
3124
+ } else {
3125
+ const newEntry = {
3126
+ patches: [reversePatch],
3127
+ inversePatches: [{ ...patch }],
3128
+ timestamp: now,
3129
+ snapshot: getSnapshot(this.target)
3130
+ };
3131
+ entries.push(newEntry);
3132
+ let newIndex = entries.length - 1;
3133
+ if (entries.length > this.maxHistoryLength) {
3134
+ const excess = entries.length - this.maxHistoryLength;
3135
+ entries = entries.slice(excess);
3136
+ newIndex -= excess;
3137
+ }
3138
+ this.lastChangeTime = now;
3139
+ return {
3140
+ ...prev,
3141
+ entries,
3142
+ currentIndex: newIndex
3143
+ };
3144
+ }
3145
+ });
3146
+ }
3147
+ undo() {
3148
+ const store = getGlobalStore();
3149
+ const state = store.get(this.historyAtom);
3150
+ if (state.currentIndex < 0) {
3151
+ return;
3152
+ }
3153
+ const node = getStateTreeNode(this.target);
3154
+ const rootNode = node.getRoot();
3155
+ const wasApplying = rootNode.$isApplyingHistory;
3156
+ rootNode.$isApplyingHistory = true;
3157
+ this.isApplyingHistory = true;
3158
+ try {
3159
+ const entry = state.entries[state.currentIndex];
3160
+ for (let i = entry.patches.length - 1; i >= 0; i--) {
3161
+ applyPatch(this.target, entry.patches[i]);
3162
+ }
3163
+ store.set(this.historyAtom, (prev) => ({
3164
+ ...prev,
3165
+ currentIndex: prev.currentIndex - 1
3166
+ }));
3167
+ } finally {
3168
+ this.isApplyingHistory = false;
3169
+ rootNode.$isApplyingHistory = wasApplying;
3170
+ }
3171
+ }
3172
+ redo() {
3173
+ const store = getGlobalStore();
3174
+ const state = store.get(this.historyAtom);
3175
+ if (state.currentIndex >= state.entries.length - 1) {
3176
+ return;
3177
+ }
3178
+ const node = getStateTreeNode(this.target);
3179
+ const rootNode = node.getRoot();
3180
+ const wasApplying = rootNode.$isApplyingHistory;
3181
+ rootNode.$isApplyingHistory = true;
3182
+ this.isApplyingHistory = true;
3183
+ try {
3184
+ const nextIndex = state.currentIndex + 1;
3185
+ const entry = state.entries[nextIndex];
3186
+ for (const patch of entry.inversePatches) {
3187
+ applyPatch(this.target, patch);
3188
+ }
3189
+ store.set(this.historyAtom, (prev) => ({
3190
+ ...prev,
3191
+ currentIndex: nextIndex
3192
+ }));
3193
+ } finally {
3194
+ this.isApplyingHistory = false;
3195
+ rootNode.$isApplyingHistory = wasApplying;
3196
+ }
3197
+ }
3198
+ goTo(index) {
3199
+ const store = getGlobalStore();
3200
+ const state = store.get(this.historyAtom);
3201
+ const maxIdx = state.entries.length;
3202
+ if (index < 0 || index > maxIdx) {
3203
+ return;
3204
+ }
3205
+ const node = getStateTreeNode(this.target);
3206
+ const rootNode = node.getRoot();
3207
+ const wasApplying = rootNode.$isApplyingHistory;
3208
+ rootNode.$isApplyingHistory = true;
3209
+ this.isApplyingHistory = true;
3210
+ try {
3211
+ const targetSnapshot = index === 0 ? state.initialSnapshot : state.entries[index - 1].snapshot;
3212
+ applySnapshot(this.target, targetSnapshot);
3213
+ store.set(this.historyAtom, (prev) => ({
3214
+ ...prev,
3215
+ currentIndex: index - 1
3216
+ }));
3217
+ } finally {
3218
+ this.isApplyingHistory = false;
3219
+ rootNode.$isApplyingHistory = wasApplying;
3220
+ }
3221
+ }
3222
+ goBack() {
3223
+ const store = getGlobalStore();
3224
+ const state = store.get(this.historyAtom);
3225
+ const currentSnapshotIndex = state.currentIndex + 1;
3226
+ if (currentSnapshotIndex > 0) {
3227
+ this.goTo(currentSnapshotIndex - 1);
3228
+ }
3229
+ }
3230
+ goForward() {
3231
+ const store = getGlobalStore();
3232
+ const state = store.get(this.historyAtom);
3233
+ const currentSnapshotIndex = state.currentIndex + 1;
3234
+ if (currentSnapshotIndex < state.entries.length) {
3235
+ this.goTo(currentSnapshotIndex + 1);
3236
+ }
3237
+ }
3238
+ record() {
3239
+ const store = getGlobalStore();
3240
+ const state = store.get(this.historyAtom);
3241
+ let entries = state.currentIndex < state.entries.length - 1 ? state.entries.slice(0, state.currentIndex + 1) : [...state.entries];
3242
+ const newEntry = {
3243
+ patches: [],
3244
+ inversePatches: [],
3245
+ timestamp: Date.now(),
3246
+ snapshot: getSnapshot(this.target)
3247
+ };
3248
+ entries.push(newEntry);
3249
+ let newIndex = entries.length - 1;
3250
+ if (entries.length > this.maxHistoryLength) {
3251
+ const excess = entries.length - this.maxHistoryLength;
3252
+ entries = entries.slice(excess);
3253
+ newIndex -= excess;
3254
+ }
3255
+ store.set(this.historyAtom, {
3256
+ ...state,
3257
+ entries,
3258
+ currentIndex: newIndex
3259
+ });
3260
+ }
3261
+ getSnapshot(index) {
3262
+ const store = getGlobalStore();
3263
+ const state = store.get(this.historyAtom);
3264
+ if (index < 0 || index > state.entries.length) {
3265
+ throw new Error(`[jotai-state-tree] Invalid snapshot index: ${index}`);
3266
+ }
3267
+ return index === 0 ? state.initialSnapshot : state.entries[index - 1].snapshot;
3268
+ }
3269
+ clear() {
3270
+ const store = getGlobalStore();
3271
+ store.set(this.historyAtom, {
3272
+ entries: [],
3273
+ currentIndex: -1,
3274
+ initialSnapshot: getSnapshot(this.target)
3275
+ });
3276
+ this.currentGroup = [];
3277
+ this.currentGroupInverse = [];
3278
+ this.grouping = false;
3279
+ this.actionGrouping = false;
3280
+ }
3281
+ startGroup() {
3282
+ this.grouping = true;
3283
+ this.actionGrouping = false;
3284
+ this.currentGroup = [];
3285
+ this.currentGroupInverse = [];
3286
+ }
3287
+ endGroup() {
3288
+ if (!this.grouping) {
3289
+ return;
3290
+ }
3291
+ this.grouping = false;
3292
+ this.actionGrouping = false;
3293
+ if (this.currentGroup.length > 0) {
3294
+ const store = getGlobalStore();
3295
+ store.set(this.historyAtom, (prev) => {
3296
+ let entries = prev.currentIndex < prev.entries.length - 1 ? prev.entries.slice(0, prev.currentIndex + 1) : [...prev.entries];
3297
+ const newEntry = {
3298
+ patches: [...this.currentGroup],
3299
+ inversePatches: [...this.currentGroupInverse],
3300
+ timestamp: Date.now(),
3301
+ snapshot: getSnapshot(this.target)
3302
+ };
3303
+ entries.push(newEntry);
3304
+ let newIndex = entries.length - 1;
3305
+ if (entries.length > this.maxHistoryLength) {
3306
+ const excess = entries.length - this.maxHistoryLength;
3307
+ entries = entries.slice(excess);
3308
+ newIndex -= excess;
3309
+ }
3310
+ return {
3311
+ ...prev,
3312
+ entries,
3313
+ currentIndex: newIndex
3314
+ };
3315
+ });
3316
+ }
3317
+ this.currentGroup = [];
3318
+ this.currentGroupInverse = [];
3319
+ }
3320
+ withoutUndo(fn) {
3321
+ this.skipRecording = true;
3322
+ try {
3323
+ return fn();
3324
+ } finally {
3325
+ this.skipRecording = false;
3326
+ }
3327
+ }
3328
+ dispose() {
3329
+ historyTrackersRegistry.delete(this.target);
3330
+ if (this.disposer) {
3331
+ this.disposer();
3332
+ this.disposer = null;
3333
+ }
3334
+ if (this.actionDisposer) {
3335
+ this.actionDisposer();
3336
+ this.actionDisposer = null;
3337
+ }
3338
+ }
3339
+ };
3340
+ function getOrCreateHistoryTracker(target, options = {}) {
3341
+ let tracker = historyTrackersRegistry.get(target);
3342
+ if (!tracker) {
3343
+ tracker = new HistoryTracker(target, options);
3344
+ historyTrackersRegistry.set(target, tracker);
3345
+ }
3346
+ return tracker;
3347
+ }
3348
+ function createUndoManager(target, options) {
3349
+ const tracker = getOrCreateHistoryTracker(target, options);
3350
+ tracker.autoRecord = true;
3351
+ const store = getGlobalStore();
3352
+ return {
3353
+ get canUndo() {
3354
+ const state = store.get(tracker.historyAtom);
3355
+ return state.currentIndex >= 0;
3356
+ },
3357
+ get canRedo() {
3358
+ const state = store.get(tracker.historyAtom);
3359
+ return state.currentIndex < state.entries.length - 1;
3360
+ },
3361
+ get undoLevels() {
3362
+ const state = store.get(tracker.historyAtom);
3363
+ return state.currentIndex + 1;
3364
+ },
3365
+ get redoLevels() {
3366
+ const state = store.get(tracker.historyAtom);
3367
+ return state.entries.length - state.currentIndex - 1;
3368
+ },
3369
+ get history() {
3370
+ const state = store.get(tracker.historyAtom);
3371
+ return state.entries;
3372
+ },
3373
+ get historyIndex() {
3374
+ const state = store.get(tracker.historyAtom);
3375
+ return state.currentIndex;
3376
+ },
3377
+ undo() {
3378
+ tracker.undo();
3379
+ },
3380
+ redo() {
3381
+ tracker.redo();
3382
+ },
3383
+ clear() {
3384
+ tracker.clear();
3385
+ },
3386
+ startGroup() {
3387
+ tracker.startGroup();
3388
+ },
3389
+ endGroup() {
3390
+ tracker.endGroup();
3391
+ },
3392
+ withoutUndo(fn) {
3393
+ return tracker.withoutUndo(fn);
3394
+ },
3395
+ dispose() {
3396
+ tracker.dispose();
3397
+ }
3398
+ };
3399
+ }
3400
+ function createTimeTravelManager(target, options) {
3401
+ const tracker = getOrCreateHistoryTracker(target, options);
3402
+ if (options?.autoRecord) {
3403
+ tracker.autoRecord = true;
3404
+ }
3405
+ const store = getGlobalStore();
3406
+ return {
3407
+ get currentIndex() {
3408
+ const state = store.get(tracker.historyAtom);
3409
+ return state.currentIndex + 1;
3410
+ },
3411
+ get snapshotCount() {
3412
+ const state = store.get(tracker.historyAtom);
3413
+ return state.entries.length + 1;
3414
+ },
3415
+ get canGoBack() {
3416
+ const state = store.get(tracker.historyAtom);
3417
+ return state.currentIndex + 1 > 0;
3418
+ },
3419
+ get canGoForward() {
3420
+ const state = store.get(tracker.historyAtom);
3421
+ return state.currentIndex + 1 < state.entries.length + 1 - 1;
3422
+ },
3423
+ record() {
3424
+ tracker.record();
3425
+ },
3426
+ goBack() {
3427
+ tracker.goBack();
3428
+ },
3429
+ goForward() {
3430
+ tracker.goForward();
3431
+ },
3432
+ goTo(index) {
3433
+ tracker.goTo(index);
3434
+ },
3435
+ getSnapshot(index) {
3436
+ return tracker.getSnapshot(index);
3437
+ },
3438
+ clear() {
3439
+ tracker.clear();
3440
+ },
3441
+ dispose() {
3442
+ tracker.dispose();
3443
+ }
3444
+ };
3445
+ }
3446
+ var ActionRecorder = class {
3447
+ constructor(target) {
3448
+ this.recording = false;
3449
+ this.recordedActions = [];
3450
+ this.disposer = null;
3451
+ this.target = target;
3452
+ }
3453
+ get isRecording() {
3454
+ return this.recording;
3455
+ }
3456
+ get actions() {
3457
+ return [...this.recordedActions];
3458
+ }
3459
+ start() {
3460
+ if (this.recording) return;
3461
+ this.recording = true;
3462
+ this.disposer = onAction(this.target, (action) => {
3463
+ this.recordedActions.push({
3464
+ ...action,
3465
+ timestamp: Date.now()
3466
+ });
3467
+ });
3468
+ }
3469
+ stop() {
3470
+ this.recording = false;
3471
+ if (this.disposer) {
3472
+ this.disposer();
3473
+ this.disposer = null;
3474
+ }
3475
+ }
3476
+ clear() {
3477
+ this.recordedActions = [];
3478
+ }
3479
+ replay(target) {
3480
+ const node = getStateTreeNode(target);
3481
+ for (const action of this.recordedActions) {
3482
+ let currentNode = node;
3483
+ if (action.path) {
3484
+ const parts = action.path.split("/").filter(Boolean);
3485
+ for (const part of parts) {
3486
+ const child = currentNode.getChild(part);
3487
+ if (!child) {
3488
+ console.warn(`[jotai-state-tree] Could not find path: ${action.path}`);
3489
+ continue;
3490
+ }
3491
+ currentNode = child;
3492
+ }
3493
+ }
3494
+ const instance = currentNode.getInstance();
3495
+ if (typeof instance[action.name] === "function") {
3496
+ instance[action.name](...action.args);
3497
+ }
3498
+ }
3499
+ }
3500
+ export() {
3501
+ return JSON.stringify(this.recordedActions, null, 2);
3502
+ }
3503
+ import(json) {
3504
+ try {
3505
+ const actions = JSON.parse(json);
3506
+ if (Array.isArray(actions)) {
3507
+ this.recordedActions = actions;
3508
+ }
3509
+ } catch (e) {
3510
+ throw new Error(`[jotai-state-tree] Failed to import actions: ${e}`);
3511
+ }
3512
+ }
3513
+ dispose() {
3514
+ this.stop();
3515
+ this.clear();
3516
+ }
3517
+ };
3518
+ function createActionRecorder(target) {
3519
+ return new ActionRecorder(target);
3520
+ }
3521
+
3522
+ // src/router.ts
3523
+ import { createContext, useContext } from "react";
3524
+ import { useAtomValue } from "jotai";
3525
+ var RouteDefinition = model("RouteDefinition", {
3526
+ path: string,
3527
+ name: string,
3528
+ meta: optional(frozen(), {})
3529
+ });
3530
+ function normalizePathname(path) {
3531
+ if (path === "/") return path;
3532
+ let normalized = path;
3533
+ if (normalized.endsWith("/")) {
3534
+ normalized = normalized.slice(0, -1);
3535
+ }
3536
+ if (!normalized.startsWith("/")) {
3537
+ normalized = "/" + normalized;
3538
+ }
3539
+ return normalized;
3540
+ }
3541
+ function parseUrl(url) {
3542
+ let pathname = url;
3543
+ let search = "";
3544
+ let hash = "";
3545
+ if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
3546
+ try {
3547
+ const fullUrl = url.startsWith("//") ? "http:" + url : url;
3548
+ const u = new URL(fullUrl);
3549
+ pathname = u.pathname;
3550
+ search = u.search;
3551
+ hash = u.hash;
3552
+ } catch {
3553
+ }
3554
+ } else {
3555
+ const hashIndex = pathname.indexOf("#");
3556
+ if (hashIndex !== -1) {
3557
+ hash = pathname.slice(hashIndex);
3558
+ pathname = pathname.slice(0, hashIndex);
3559
+ }
3560
+ const queryIndex = pathname.indexOf("?");
3561
+ if (queryIndex !== -1) {
3562
+ search = pathname.slice(queryIndex);
3563
+ pathname = pathname.slice(0, queryIndex);
3564
+ }
3565
+ }
3566
+ const query = {};
3567
+ if (search && search.length > 1) {
3568
+ const pairs = search.slice(1).split("&");
3569
+ for (const pair of pairs) {
3570
+ const [key, value] = pair.split("=");
3571
+ if (key) {
3572
+ query[decodeURIComponent(key)] = value ? decodeURIComponent(value) : "";
3573
+ }
3574
+ }
3575
+ }
3576
+ return {
3577
+ pathname: normalizePathname(pathname),
3578
+ search,
3579
+ hash,
3580
+ query
3581
+ };
3582
+ }
3583
+ function matchPath(pattern, pathname) {
3584
+ const normalizedPattern = normalizePathname(pattern);
3585
+ const normalizedPathname = normalizePathname(pathname);
3586
+ if (normalizedPattern === "/*" || normalizedPattern === "*") {
3587
+ return { "*": normalizedPathname };
3588
+ }
3589
+ const patternSegments = normalizedPattern.split("/").filter(Boolean);
3590
+ const pathnameSegments = normalizedPathname.split("/").filter(Boolean);
3591
+ const hasWildcard = patternSegments[patternSegments.length - 1] === "*";
3592
+ if (!hasWildcard && patternSegments.length !== pathnameSegments.length) {
3593
+ return null;
3594
+ }
3595
+ const params = {};
3596
+ const limit = hasWildcard ? patternSegments.length - 1 : patternSegments.length;
3597
+ for (let i = 0; i < limit; i++) {
3598
+ const pSeg = patternSegments[i];
3599
+ const pathSeg = pathnameSegments[i];
3600
+ if (!pathSeg) return null;
3601
+ if (pSeg.startsWith(":")) {
3602
+ params[pSeg.slice(1)] = decodeURIComponent(pathSeg);
3603
+ } else if (pSeg !== pathSeg) {
3604
+ return null;
3605
+ }
3606
+ }
3607
+ if (hasWildcard) {
3608
+ const wildcardSegments = pathnameSegments.slice(limit);
3609
+ params["*"] = "/" + wildcardSegments.map(decodeURIComponent).join("/");
3610
+ }
3611
+ return params;
3612
+ }
3613
+ function matchRoutes(routes, pathname) {
3614
+ const normalizedPathname = normalizePathname(pathname);
3615
+ for (const route of routes) {
3616
+ const params = matchPath(route.path, normalizedPathname);
3617
+ if (params) {
3618
+ return {
3619
+ route,
3620
+ params
3621
+ };
3622
+ }
3623
+ }
3624
+ return null;
3625
+ }
3626
+ var RouterModel = model("RouterModel", {
3627
+ pathname: string,
3628
+ search: string,
3629
+ hash: string,
3630
+ action: enumeration("NavigationAction", ["PUSH", "REPLACE", "POP", "INITIAL"]),
3631
+ params: optional(frozen(), {}),
3632
+ query: optional(frozen(), {}),
3633
+ currentRouteName: optional(maybeNull(string), null),
3634
+ routes: array(RouteDefinition)
3635
+ }).views((self) => ({
3636
+ get currentRoute() {
3637
+ if (!self.currentRouteName) return null;
3638
+ return self.routes.find((r) => r.name === self.currentRouteName) || null;
3639
+ },
3640
+ isActive(routeName, params) {
3641
+ if (self.currentRouteName !== routeName) return false;
3642
+ if (params) {
3643
+ for (const [key, val] of Object.entries(params)) {
3644
+ if (self.params[key] !== val) return false;
3645
+ }
3646
+ }
3647
+ return true;
3648
+ }
3649
+ })).volatile(() => ({
3650
+ beforeNavigate: null,
3651
+ afterNavigate: null,
3652
+ _popStateListener: null
3653
+ })).actions((self) => {
3654
+ return {
3655
+ setGuards(before, after) {
3656
+ self.beforeNavigate = before;
3657
+ self.afterNavigate = after;
3658
+ },
3659
+ setPopStateListener(listener) {
3660
+ self._popStateListener = listener;
3661
+ },
3662
+ syncLocation(pathname, search, hash, action, state) {
3663
+ self.pathname = pathname;
3664
+ self.search = search;
3665
+ self.hash = hash;
3666
+ self.action = action;
3667
+ const parsed = parseUrl(pathname + search + hash);
3668
+ const matched = matchRoutes(self.routes, parsed.pathname);
3669
+ self.params = matched ? matched.params : {};
3670
+ self.query = parsed.query;
3671
+ self.currentRouteName = matched ? matched.route.name : null;
3672
+ if (typeof window !== "undefined" && window.history) {
3673
+ const fullPath = pathname + search + hash;
3674
+ if (action === "PUSH") {
3675
+ window.history.pushState(state, "", fullPath);
3676
+ } else if (action === "REPLACE") {
3677
+ window.history.replaceState(state, "", fullPath);
3678
+ }
3679
+ }
3680
+ },
3681
+ push: flow(function* (path, state) {
3682
+ const parsed = parseUrl(path);
3683
+ const matched = matchRoutes(self.routes, parsed.pathname);
3684
+ const from = {
3685
+ pathname: self.pathname,
3686
+ search: self.search,
3687
+ hash: self.hash,
3688
+ params: self.params,
3689
+ query: self.query,
3690
+ currentRouteName: self.currentRouteName
3691
+ };
3692
+ const to = {
3693
+ pathname: parsed.pathname,
3694
+ search: parsed.search,
3695
+ hash: parsed.hash,
3696
+ params: matched ? matched.params : {},
3697
+ query: parsed.query,
3698
+ currentRouteName: matched ? matched.route.name : null,
3699
+ state
3700
+ };
3701
+ if (self.beforeNavigate) {
3702
+ const result = yield Promise.resolve(self.beforeNavigate(from, to));
3703
+ if (result === false) {
3704
+ return;
3705
+ }
3706
+ if (typeof result === "string") {
3707
+ yield self.push(result, state);
3708
+ return;
3709
+ }
3710
+ }
3711
+ self.syncLocation(to.pathname, to.search, to.hash, "PUSH", state);
3712
+ if (self.afterNavigate) {
3713
+ self.afterNavigate(to);
3714
+ }
3715
+ }),
3716
+ replace: flow(function* (path, state) {
3717
+ const parsed = parseUrl(path);
3718
+ const matched = matchRoutes(self.routes, parsed.pathname);
3719
+ const from = {
3720
+ pathname: self.pathname,
3721
+ search: self.search,
3722
+ hash: self.hash,
3723
+ params: self.params,
3724
+ query: self.query,
3725
+ currentRouteName: self.currentRouteName
3726
+ };
3727
+ const to = {
3728
+ pathname: parsed.pathname,
3729
+ search: parsed.search,
3730
+ hash: parsed.hash,
3731
+ params: matched ? matched.params : {},
3732
+ query: parsed.query,
3733
+ currentRouteName: matched ? matched.route.name : null,
3734
+ state
3735
+ };
3736
+ if (self.beforeNavigate) {
3737
+ const result = yield Promise.resolve(self.beforeNavigate(from, to));
3738
+ if (result === false) {
3739
+ return;
3740
+ }
3741
+ if (typeof result === "string") {
3742
+ yield self.replace(result, state);
3743
+ return;
3744
+ }
3745
+ }
3746
+ self.syncLocation(to.pathname, to.search, to.hash, "REPLACE", state);
3747
+ if (self.afterNavigate) {
3748
+ self.afterNavigate(to);
3749
+ }
3750
+ }),
3751
+ go(delta) {
3752
+ if (typeof window !== "undefined" && window.history) {
3753
+ window.history.go(delta);
3754
+ }
3755
+ },
3756
+ goBack() {
3757
+ if (typeof window !== "undefined" && window.history) {
3758
+ window.history.back();
3759
+ }
3760
+ },
3761
+ goForward() {
3762
+ if (typeof window !== "undefined" && window.history) {
3763
+ window.history.forward();
3764
+ }
3765
+ }
3766
+ };
3767
+ }).afterCreate((self) => {
3768
+ if (typeof window !== "undefined") {
3769
+ const handlePopState = (event) => {
3770
+ const parsed = parseUrl(window.location.pathname + window.location.search + window.location.hash);
3771
+ const matched = matchRoutes(self.routes, parsed.pathname);
3772
+ const from = {
3773
+ pathname: self.pathname,
3774
+ search: self.search,
3775
+ hash: self.hash,
3776
+ params: self.params,
3777
+ query: self.query,
3778
+ currentRouteName: self.currentRouteName
3779
+ };
3780
+ const to = {
3781
+ pathname: parsed.pathname,
3782
+ search: parsed.search,
3783
+ hash: parsed.hash,
3784
+ params: matched ? matched.params : {},
3785
+ query: parsed.query,
3786
+ currentRouteName: matched ? matched.route.name : null,
3787
+ state: event.state
3788
+ };
3789
+ const proceed = () => {
3790
+ self.syncLocation(to.pathname, to.search, to.hash, "POP", event.state);
3791
+ if (self.afterNavigate) {
3792
+ self.afterNavigate(to);
3793
+ }
3794
+ };
3795
+ const revert = () => {
3796
+ const fullPath = from.pathname + from.search + from.hash;
3797
+ window.history.replaceState(null, "", fullPath);
3798
+ };
3799
+ if (self.beforeNavigate) {
3800
+ const result = self.beforeNavigate(from, to);
3801
+ if (result instanceof Promise) {
3802
+ result.then((res) => {
3803
+ if (res === false) {
3804
+ revert();
3805
+ } else if (typeof res === "string") {
3806
+ self.push(res);
3807
+ } else {
3808
+ proceed();
3809
+ }
3810
+ }).catch(() => {
3811
+ revert();
3812
+ });
3813
+ } else {
3814
+ if (result === false) {
3815
+ revert();
3816
+ } else if (typeof result === "string") {
3817
+ self.push(result);
3818
+ } else {
3819
+ proceed();
3820
+ }
3821
+ }
3822
+ } else {
3823
+ proceed();
3824
+ }
3825
+ };
3826
+ window.addEventListener("popstate", handlePopState);
3827
+ self.setPopStateListener(handlePopState);
3828
+ }
3829
+ }).beforeDestroy((self) => {
3830
+ if (typeof window !== "undefined" && self._popStateListener) {
3831
+ window.removeEventListener("popstate", self._popStateListener);
3832
+ self.setPopStateListener(null);
3833
+ }
3834
+ });
3835
+ function createRouter(config) {
3836
+ let initialPathname = "/";
3837
+ let initialSearch = "";
3838
+ let initialHash = "";
3839
+ if (config.initialUrl) {
3840
+ const parsed = parseUrl(config.initialUrl);
3841
+ initialPathname = parsed.pathname;
3842
+ initialSearch = parsed.search;
3843
+ initialHash = parsed.hash;
3844
+ } else if (typeof window !== "undefined") {
3845
+ initialPathname = window.location.pathname;
3846
+ initialSearch = window.location.search;
3847
+ initialHash = window.location.hash;
3848
+ }
3849
+ const router = RouterModel.create({
3850
+ pathname: initialPathname,
3851
+ search: initialSearch,
3852
+ hash: initialHash,
3853
+ action: "INITIAL",
3854
+ currentRouteName: null,
3855
+ routes: config.routes.map((r) => ({
3856
+ path: r.path,
3857
+ name: r.name,
3858
+ meta: r.meta || {}
3859
+ }))
3860
+ });
3861
+ router.setGuards(config.beforeNavigate, config.afterNavigate);
3862
+ router.syncLocation(initialPathname, initialSearch, initialHash, "INITIAL");
3863
+ return router;
3864
+ }
3865
+ var RouterContext = createContext(null);
3866
+ function useRouter() {
3867
+ const router = useContext(RouterContext);
3868
+ if (!router) {
3869
+ throw new Error("[jotai-state-tree] useRouter must be used within a RouterContext.Provider");
3870
+ }
3871
+ if (hasStateTreeNode(router)) {
3872
+ const node = getStateTreeNode(router);
3873
+ useAtomValue(node.snapshotAtom, { store: getGlobalStore() });
3874
+ }
3875
+ return router;
3876
+ }
3877
+
3878
+ export {
3879
+ string,
3880
+ number,
3881
+ integer,
3882
+ boolean,
3883
+ DatePrimitive,
3884
+ nullType,
3885
+ undefinedType,
3886
+ identifier,
3887
+ identifierNumber,
3888
+ literal,
3889
+ enumeration,
3890
+ frozen,
3891
+ custom,
3892
+ finite,
3893
+ float,
3894
+ getIsApplyingSnapshotOrPatch,
3895
+ setIsApplyingSnapshotOrPatch,
3896
+ getGlobalStore,
3897
+ setGlobalStore,
3898
+ resetGlobalStore,
3899
+ getActiveTrackingFn,
3900
+ setActiveTrackingFn,
3901
+ onLifecycleChange,
3902
+ StateTreeNode,
3903
+ $treenode,
3904
+ getStateTreeNode,
3905
+ hasStateTreeNode,
3906
+ applySnapshotToNode,
3907
+ resolveIdentifier,
3908
+ getRegistryStats,
3909
+ cleanupStaleEntries,
3910
+ clearAllRegistries,
3911
+ getRoot,
3912
+ getParent,
3913
+ tryGetParent,
3914
+ hasParent,
3915
+ getParentOfType,
3916
+ getPath,
3917
+ getPathParts,
3918
+ getEnv,
3919
+ isAlive,
3920
+ isRoot,
3921
+ getType,
3922
+ isStateTreeNode,
3923
+ getIdentifier,
3924
+ destroy,
3925
+ detach,
3926
+ clone,
3927
+ getSnapshot,
3928
+ applySnapshot,
3929
+ onSnapshot,
3930
+ onPatch,
3931
+ applyPatch,
3932
+ recordPatches,
3933
+ onAction,
3934
+ walk,
3935
+ getMembers,
3936
+ resolvePath,
3937
+ tryResolve,
3938
+ getRelativePath,
3939
+ isAncestor,
3940
+ haveSameRoot,
3941
+ findAll,
3942
+ findFirst,
3943
+ isValidReference,
3944
+ getTreeStats,
3945
+ cloneDeep,
3946
+ getOrCreatePath,
3947
+ freeze,
3948
+ isFrozen,
3949
+ unfreeze,
3950
+ addMiddleware,
3951
+ flow,
3952
+ recordActions,
3953
+ protect,
3954
+ unprotect,
3955
+ isProtected,
3956
+ canWrite,
3957
+ applyAction,
3958
+ escapeJsonPath,
3959
+ unescapeJsonPath,
3960
+ splitJsonPath,
3961
+ joinJsonPath,
3962
+ model,
3963
+ compose,
3964
+ mixin,
3965
+ array,
3966
+ optional,
3967
+ maybe,
3968
+ maybeNull,
3969
+ union,
3970
+ late,
3971
+ refinement,
3972
+ reference,
3973
+ safeReference,
3974
+ snapshotProcessor,
3975
+ getOrCreateHistoryTracker,
3976
+ createUndoManager,
3977
+ createTimeTravelManager,
3978
+ createActionRecorder,
3979
+ RouteDefinition,
3980
+ RouterModel,
3981
+ createRouter,
3982
+ RouterContext,
3983
+ useRouter
3984
+ };