jotai-state-tree 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js ADDED
@@ -0,0 +1,1259 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/tree.ts
34
+ var tree_exports = {};
35
+ __export(tree_exports, {
36
+ $treenode: () => $treenode,
37
+ StateTreeNode: () => StateTreeNode,
38
+ applyPatch: () => applyPatch,
39
+ applySnapshot: () => applySnapshot,
40
+ applySnapshotToNode: () => applySnapshotToNode,
41
+ cleanupStaleEntries: () => cleanupStaleEntries,
42
+ clearAllRegistries: () => clearAllRegistries,
43
+ clone: () => clone,
44
+ cloneDeep: () => cloneDeep,
45
+ destroy: () => destroy,
46
+ detach: () => detach,
47
+ findAll: () => findAll,
48
+ findFirst: () => findFirst,
49
+ freeze: () => freeze,
50
+ getEnv: () => getEnv,
51
+ getGlobalStore: () => getGlobalStore,
52
+ getIdentifier: () => getIdentifier,
53
+ getMembers: () => getMembers,
54
+ getNodesOfType: () => getNodesOfType,
55
+ getOrCreatePath: () => getOrCreatePath,
56
+ getParent: () => getParent,
57
+ getParentOfType: () => getParentOfType,
58
+ getPath: () => getPath,
59
+ getPathParts: () => getPathParts,
60
+ getRegistryStats: () => getRegistryStats,
61
+ getRelativePath: () => getRelativePath,
62
+ getRoot: () => getRoot,
63
+ getSnapshot: () => getSnapshot,
64
+ getSnapshotFromNode: () => getSnapshotFromNode,
65
+ getStateTreeNode: () => getStateTreeNode,
66
+ getTreeStats: () => getTreeStats,
67
+ getType: () => getType,
68
+ hasParent: () => hasParent,
69
+ hasStateTreeNode: () => hasStateTreeNode,
70
+ haveSameRoot: () => haveSameRoot,
71
+ isAlive: () => isAlive,
72
+ isAncestor: () => isAncestor,
73
+ isFrozen: () => isFrozen,
74
+ isRoot: () => isRoot,
75
+ isStateTreeNode: () => isStateTreeNode,
76
+ isValidReference: () => isValidReference,
77
+ onAction: () => onAction,
78
+ onLifecycleChange: () => onLifecycleChange,
79
+ onPatch: () => onPatch,
80
+ onSnapshot: () => onSnapshot,
81
+ recordPatches: () => recordPatches,
82
+ registerActionRecorderHook: () => registerActionRecorderHook,
83
+ resetGlobalStore: () => resetGlobalStore,
84
+ resolveIdentifier: () => resolveIdentifier,
85
+ resolvePath: () => resolvePath,
86
+ setGlobalStore: () => setGlobalStore,
87
+ trackAction: () => trackAction,
88
+ tryGetParent: () => tryGetParent,
89
+ tryResolve: () => tryResolve,
90
+ unfreeze: () => unfreeze,
91
+ walk: () => walk
92
+ });
93
+ function getGlobalStore() {
94
+ return globalStore;
95
+ }
96
+ function setGlobalStore(store) {
97
+ globalStore = store;
98
+ }
99
+ function resetGlobalStore() {
100
+ globalStore = (0, import_jotai.createStore)();
101
+ }
102
+ function generateNodeId() {
103
+ return `node_${++nodeIdCounter}_${Date.now().toString(36)}`;
104
+ }
105
+ function onLifecycleChange(node, listener) {
106
+ let listeners = lifecycleListeners.get(node);
107
+ if (!listeners) {
108
+ listeners = /* @__PURE__ */ new Set();
109
+ lifecycleListeners.set(node, listeners);
110
+ }
111
+ listeners.add(listener);
112
+ return () => {
113
+ listeners?.delete(listener);
114
+ };
115
+ }
116
+ function notifyLifecycleChange(node, isAlive2) {
117
+ const listeners = lifecycleListeners.get(node);
118
+ if (listeners) {
119
+ listeners.forEach((listener) => listener(isAlive2));
120
+ }
121
+ }
122
+ function getStateTreeNode(instance) {
123
+ if (instance && typeof instance === "object" && $treenode in instance) {
124
+ return instance[$treenode];
125
+ }
126
+ throw new Error("[jotai-state-tree] Value is not a state tree node");
127
+ }
128
+ function hasStateTreeNode(instance) {
129
+ return instance !== null && typeof instance === "object" && $treenode in instance;
130
+ }
131
+ function getSnapshotFromNode(node) {
132
+ const type = node.$type;
133
+ const value = node.getValue();
134
+ if (type._kind === "model") {
135
+ const snapshot = {};
136
+ const children = node.getChildren();
137
+ for (const [key, childNode] of children) {
138
+ snapshot[key] = getSnapshotFromNode(childNode);
139
+ }
140
+ if (node.postProcessor) {
141
+ return node.postProcessor(snapshot);
142
+ }
143
+ return snapshot;
144
+ }
145
+ if (type._kind === "array") {
146
+ const arr = value;
147
+ return arr.map((_, index) => {
148
+ const childNode = node.getChild(String(index));
149
+ return childNode ? getSnapshotFromNode(childNode) : arr[index];
150
+ });
151
+ }
152
+ if (type._kind === "map") {
153
+ const snapshot = {};
154
+ const children = node.getChildren();
155
+ for (const [key, childNode] of children) {
156
+ snapshot[key] = getSnapshotFromNode(childNode);
157
+ }
158
+ return snapshot;
159
+ }
160
+ if (type._kind === "reference") {
161
+ return node.identifierValue ?? value;
162
+ }
163
+ return value;
164
+ }
165
+ function applySnapshotToNode(node, snapshot) {
166
+ if (!node.$isAlive) {
167
+ throw new Error("[jotai-state-tree] Cannot apply snapshot to a dead node");
168
+ }
169
+ const type = node.$type;
170
+ if (node.preProcessor) {
171
+ snapshot = node.preProcessor(snapshot);
172
+ }
173
+ if (type._kind === "model" && typeof snapshot === "object" && snapshot !== null) {
174
+ const snapshotObj = snapshot;
175
+ const children = node.getChildren();
176
+ for (const [key, childNode] of children) {
177
+ if (key in snapshotObj) {
178
+ applySnapshotToNode(childNode, snapshotObj[key]);
179
+ }
180
+ }
181
+ } else if (type._kind === "array" && Array.isArray(snapshot)) {
182
+ node.setValue(snapshot);
183
+ } else if (type._kind === "map" && typeof snapshot === "object" && snapshot !== null) {
184
+ node.setValue(snapshot);
185
+ } else {
186
+ node.setValue(snapshot);
187
+ }
188
+ }
189
+ function resolveIdentifier(typeName, identifier) {
190
+ const weakRef = identifierRegistry.get(typeName)?.get(identifier);
191
+ return weakRef?.deref();
192
+ }
193
+ function getNodesOfType(typeName) {
194
+ const typeMap = identifierRegistry.get(typeName);
195
+ if (!typeMap) return [];
196
+ const nodes = [];
197
+ for (const weakRef of typeMap.values()) {
198
+ const node = weakRef.deref();
199
+ if (node) {
200
+ nodes.push(node);
201
+ }
202
+ }
203
+ return nodes;
204
+ }
205
+ function getRegistryStats() {
206
+ let liveNodeCount = 0;
207
+ let staleNodeCount = 0;
208
+ for (const entry of nodeRegistry.values()) {
209
+ const node = entry.node.deref();
210
+ if (node && node.$isAlive) {
211
+ liveNodeCount++;
212
+ } else {
213
+ staleNodeCount++;
214
+ }
215
+ }
216
+ let identifierCount = 0;
217
+ for (const typeMap of identifierRegistry.values()) {
218
+ for (const weakRef of typeMap.values()) {
219
+ const node = weakRef.deref();
220
+ if (node && node.$isAlive) {
221
+ identifierCount++;
222
+ }
223
+ }
224
+ }
225
+ return {
226
+ nodeRegistrySize: nodeRegistry.size,
227
+ identifierRegistrySize: identifierCount,
228
+ identifierTypeCount: identifierRegistry.size,
229
+ liveNodeCount,
230
+ staleNodeCount
231
+ };
232
+ }
233
+ function cleanupStaleEntries() {
234
+ let cleaned = 0;
235
+ for (const [id, entry] of nodeRegistry.entries()) {
236
+ if (!entry.node.deref()) {
237
+ nodeRegistry.delete(id);
238
+ cleaned++;
239
+ }
240
+ }
241
+ for (const [typeName, typeMap] of identifierRegistry.entries()) {
242
+ for (const [identifier, weakRef] of typeMap.entries()) {
243
+ if (!weakRef.deref()) {
244
+ typeMap.delete(identifier);
245
+ cleaned++;
246
+ }
247
+ }
248
+ if (typeMap.size === 0) {
249
+ identifierRegistry.delete(typeName);
250
+ }
251
+ }
252
+ return cleaned;
253
+ }
254
+ function clearAllRegistries() {
255
+ for (const entry of nodeRegistry.values()) {
256
+ const node = entry.node.deref();
257
+ if (node) {
258
+ node.$isAlive = false;
259
+ }
260
+ }
261
+ nodeRegistry.clear();
262
+ identifierRegistry.clear();
263
+ nodeIdCounter = 0;
264
+ }
265
+ function getRoot(target) {
266
+ const node = getStateTreeNode(target);
267
+ const rootNode = node.getRoot();
268
+ return rootNode.getInstance();
269
+ }
270
+ function getParent(target, depth = 1) {
271
+ let node = getStateTreeNode(target);
272
+ for (let i = 0; i < depth; i++) {
273
+ if (!node.$parent) {
274
+ throw new Error("[jotai-state-tree] Cannot get parent of root node");
275
+ }
276
+ node = node.$parent;
277
+ }
278
+ return node.getInstance();
279
+ }
280
+ function tryGetParent(target, depth = 1) {
281
+ try {
282
+ return getParent(target, depth);
283
+ } catch {
284
+ return void 0;
285
+ }
286
+ }
287
+ function hasParent(target, depth = 1) {
288
+ let node = getStateTreeNode(target);
289
+ for (let i = 0; i < depth; i++) {
290
+ if (!node.$parent) return false;
291
+ node = node.$parent;
292
+ }
293
+ return true;
294
+ }
295
+ function getParentOfType(target, type) {
296
+ let node = getStateTreeNode(target).$parent;
297
+ while (node) {
298
+ if (node.$type === type || node.$type.name === type.name) {
299
+ return node.getInstance();
300
+ }
301
+ node = node.$parent;
302
+ }
303
+ throw new Error(`[jotai-state-tree] No parent of type '${type.name}' found`);
304
+ }
305
+ function getPath(target) {
306
+ return getStateTreeNode(target).$path;
307
+ }
308
+ function getPathParts(target) {
309
+ const path = getPath(target);
310
+ return path ? path.split("/").filter(Boolean) : [];
311
+ }
312
+ function getEnv(target) {
313
+ return getStateTreeNode(target).$env;
314
+ }
315
+ function isAlive(target) {
316
+ try {
317
+ return getStateTreeNode(target).$isAlive;
318
+ } catch {
319
+ return false;
320
+ }
321
+ }
322
+ function isRoot(target) {
323
+ return getStateTreeNode(target).$parent === null;
324
+ }
325
+ function getType(target) {
326
+ return getStateTreeNode(target).$type;
327
+ }
328
+ function isStateTreeNode(value) {
329
+ return hasStateTreeNode(value);
330
+ }
331
+ function getIdentifier(target) {
332
+ const node = getStateTreeNode(target);
333
+ return node.identifierValue ?? null;
334
+ }
335
+ function destroy(target) {
336
+ const node = getStateTreeNode(target);
337
+ node.destroy();
338
+ }
339
+ function detach(target) {
340
+ const node = getStateTreeNode(target);
341
+ node.detach();
342
+ return target;
343
+ }
344
+ function clone(target, keepEnvironment = true) {
345
+ const node = getStateTreeNode(target);
346
+ const snapshot = getSnapshotFromNode(node);
347
+ const type = node.$type;
348
+ return type.create(snapshot, keepEnvironment ? node.$env : void 0);
349
+ }
350
+ function getSnapshot(target) {
351
+ const node = getStateTreeNode(target);
352
+ return getSnapshotFromNode(node);
353
+ }
354
+ function applySnapshot(target, snapshot) {
355
+ const node = getStateTreeNode(target);
356
+ applySnapshotToNode(node, snapshot);
357
+ }
358
+ function onSnapshot(target, listener) {
359
+ const node = getStateTreeNode(target);
360
+ return node.onSnapshot(listener);
361
+ }
362
+ function onPatch(target, listener) {
363
+ const node = getStateTreeNode(target);
364
+ return node.onPatch(listener);
365
+ }
366
+ function applyPatch(target, patch) {
367
+ const patches = Array.isArray(patch) ? patch : [patch];
368
+ const rootNode = getStateTreeNode(target).getRoot();
369
+ for (const p of patches) {
370
+ applyPatchToNode(rootNode, p);
371
+ }
372
+ }
373
+ function applyPatchToNode(rootNode, patch) {
374
+ const pathParts = patch.path.split("/").filter(Boolean);
375
+ let node = rootNode;
376
+ for (let i = 0; i < pathParts.length - 1; i++) {
377
+ const childNode = node.getChild(pathParts[i]);
378
+ if (!childNode) {
379
+ throw new Error(`[jotai-state-tree] Invalid patch path: ${patch.path}`);
380
+ }
381
+ node = childNode;
382
+ }
383
+ const key = pathParts[pathParts.length - 1];
384
+ switch (patch.op) {
385
+ case "replace": {
386
+ const childNode = node.getChild(key);
387
+ if (childNode) {
388
+ applySnapshotToNode(childNode, patch.value);
389
+ } else {
390
+ const currentValue = node.getValue();
391
+ currentValue[key] = patch.value;
392
+ node.setValue(currentValue);
393
+ }
394
+ break;
395
+ }
396
+ case "add": {
397
+ const currentValue = node.getValue();
398
+ if (Array.isArray(currentValue)) {
399
+ const index = key === "-" ? currentValue.length : parseInt(key, 10);
400
+ currentValue.splice(index, 0, patch.value);
401
+ node.setValue([...currentValue]);
402
+ } else if (typeof currentValue === "object" && currentValue !== null) {
403
+ currentValue[key] = patch.value;
404
+ node.setValue({ ...currentValue });
405
+ }
406
+ break;
407
+ }
408
+ case "remove": {
409
+ const currentValue = node.getValue();
410
+ if (Array.isArray(currentValue)) {
411
+ const index = parseInt(key, 10);
412
+ currentValue.splice(index, 1);
413
+ node.setValue([...currentValue]);
414
+ } else if (typeof currentValue === "object" && currentValue !== null) {
415
+ delete currentValue[key];
416
+ node.setValue({ ...currentValue });
417
+ }
418
+ break;
419
+ }
420
+ }
421
+ }
422
+ function recordPatches(target) {
423
+ const patches = [];
424
+ const inversePatches = [];
425
+ let recording = true;
426
+ const disposer = onPatch(target, (patch, reversePatch) => {
427
+ if (recording) {
428
+ patches.push(patch);
429
+ inversePatches.push(reversePatch);
430
+ }
431
+ });
432
+ return {
433
+ patches,
434
+ inversePatches,
435
+ stop: () => {
436
+ recording = false;
437
+ disposer();
438
+ },
439
+ resume: () => {
440
+ recording = true;
441
+ },
442
+ replay: (t) => {
443
+ applyPatch(t, patches);
444
+ },
445
+ undo: (t) => {
446
+ applyPatch(t, inversePatches.slice().reverse());
447
+ }
448
+ };
449
+ }
450
+ function registerActionRecorderHook(hook) {
451
+ actionRecorderHooks.push(hook);
452
+ return () => {
453
+ const index = actionRecorderHooks.indexOf(hook);
454
+ if (index >= 0) {
455
+ actionRecorderHooks.splice(index, 1);
456
+ }
457
+ };
458
+ }
459
+ function trackAction(node, name, args, fn) {
460
+ const previousAction = currentAction;
461
+ currentAction = { name, args, tree: node };
462
+ try {
463
+ const result = fn();
464
+ const call = {
465
+ name,
466
+ path: node.$path,
467
+ args
468
+ };
469
+ actionListeners.forEach((listener) => listener(call));
470
+ actionRecorderHooks.forEach((hook) => hook(node, call));
471
+ return result;
472
+ } finally {
473
+ currentAction = previousAction;
474
+ }
475
+ }
476
+ function onAction(target, listener) {
477
+ actionListeners.add(listener);
478
+ return () => {
479
+ actionListeners.delete(listener);
480
+ };
481
+ }
482
+ function walk(target, visitor) {
483
+ const treeNode = getStateTreeNode(target);
484
+ function visitNode(node) {
485
+ const instance = node.getInstance();
486
+ if (instance) {
487
+ visitor(instance);
488
+ }
489
+ node.getChildren().forEach(visitNode);
490
+ }
491
+ visitNode(treeNode);
492
+ }
493
+ function getMembers(target) {
494
+ const result = [];
495
+ const node = getStateTreeNode(target);
496
+ const instance = target;
497
+ for (const [key] of node.getChildren()) {
498
+ result.push({
499
+ name: key,
500
+ type: "property",
501
+ value: instance[key]
502
+ });
503
+ }
504
+ for (const [key, value] of Object.entries(node.volatileState)) {
505
+ result.push({
506
+ name: key,
507
+ type: "volatile",
508
+ value
509
+ });
510
+ }
511
+ return result;
512
+ }
513
+ function resolvePath(target, path) {
514
+ const parts = path.split("/").filter(Boolean);
515
+ let node = getStateTreeNode(target);
516
+ for (const part of parts) {
517
+ const child = node.getChild(part);
518
+ if (!child) {
519
+ throw new Error(`[jotai-state-tree] Invalid path: ${path}`);
520
+ }
521
+ node = child;
522
+ }
523
+ return node.getInstance();
524
+ }
525
+ function tryResolve(target, path) {
526
+ try {
527
+ return resolvePath(target, path);
528
+ } catch {
529
+ return void 0;
530
+ }
531
+ }
532
+ function getRelativePath(from, to) {
533
+ const fromNode = getStateTreeNode(from);
534
+ const toNode = getStateTreeNode(to);
535
+ const fromParts = fromNode.$path.split("/").filter(Boolean);
536
+ const toParts = toNode.$path.split("/").filter(Boolean);
537
+ let commonLength = 0;
538
+ for (let i = 0; i < Math.min(fromParts.length, toParts.length); i++) {
539
+ if (fromParts[i] === toParts[i]) {
540
+ commonLength++;
541
+ } else {
542
+ break;
543
+ }
544
+ }
545
+ const upCount = fromParts.length - commonLength;
546
+ const downParts = toParts.slice(commonLength);
547
+ const parts = [];
548
+ for (let i = 0; i < upCount; i++) {
549
+ parts.push("..");
550
+ }
551
+ parts.push(...downParts);
552
+ return parts.join("/") || ".";
553
+ }
554
+ function isAncestor(ancestor, descendant) {
555
+ const ancestorNode = getStateTreeNode(ancestor);
556
+ let currentNode = getStateTreeNode(descendant);
557
+ while (currentNode) {
558
+ if (currentNode === ancestorNode) {
559
+ return true;
560
+ }
561
+ currentNode = currentNode.$parent;
562
+ }
563
+ return false;
564
+ }
565
+ function haveSameRoot(a, b) {
566
+ return getRoot(a) === getRoot(b);
567
+ }
568
+ function findAll(target, predicate) {
569
+ const results = [];
570
+ walk(target, (node) => {
571
+ if (predicate(node)) {
572
+ results.push(node);
573
+ }
574
+ });
575
+ return results;
576
+ }
577
+ function findFirst(target, predicate) {
578
+ let result;
579
+ walk(target, (node) => {
580
+ if (!result && predicate(node)) {
581
+ result = node;
582
+ }
583
+ });
584
+ return result;
585
+ }
586
+ function isValidReference(target, identifier) {
587
+ if (!hasStateTreeNode(target)) return false;
588
+ const node = getStateTreeNode(target);
589
+ const typeName = node.$type.name;
590
+ try {
591
+ const resolved = resolveIdentifier(typeName, identifier);
592
+ return resolved !== void 0;
593
+ } catch {
594
+ return false;
595
+ }
596
+ }
597
+ function getTreeStats(target) {
598
+ let nodeCount = 0;
599
+ let maxDepth = 0;
600
+ const types = {};
601
+ walk(target, (node) => {
602
+ if (!hasStateTreeNode(node)) return;
603
+ const stateNode = getStateTreeNode(node);
604
+ nodeCount++;
605
+ const depth = stateNode.$path.split("/").filter(Boolean).length;
606
+ maxDepth = Math.max(maxDepth, depth);
607
+ const typeName = stateNode.$type.name;
608
+ types[typeName] = (types[typeName] || 0) + 1;
609
+ });
610
+ return {
611
+ nodeCount,
612
+ depth: maxDepth,
613
+ types
614
+ };
615
+ }
616
+ function cloneDeep(target) {
617
+ const snapshot = getSnapshot(target);
618
+ const node = getStateTreeNode(target);
619
+ return node.$type.create(snapshot, node.$env);
620
+ }
621
+ function getOrCreatePath(target, path, creator) {
622
+ const parts = path.split("/").filter(Boolean);
623
+ let node = getStateTreeNode(target);
624
+ for (let i = 0; i < parts.length; i++) {
625
+ const part = parts[i];
626
+ let child = node.getChild(part);
627
+ if (!child && i === parts.length - 1) {
628
+ const instance = creator();
629
+ if (hasStateTreeNode(instance)) {
630
+ child = getStateTreeNode(instance);
631
+ node.addChild(part, child);
632
+ } else {
633
+ throw new Error(
634
+ "[jotai-state-tree] Creator must return a state tree node"
635
+ );
636
+ }
637
+ }
638
+ if (!child) {
639
+ throw new Error(`[jotai-state-tree] Invalid path: ${path}`);
640
+ }
641
+ node = child;
642
+ }
643
+ return node.getInstance();
644
+ }
645
+ function freeze(target) {
646
+ const node = getStateTreeNode(target);
647
+ node.volatileState.$frozen = true;
648
+ for (const [, child] of node.getChildren()) {
649
+ const instance = child.getInstance();
650
+ if (instance && hasStateTreeNode(instance)) {
651
+ freeze(instance);
652
+ }
653
+ }
654
+ }
655
+ function isFrozen(target) {
656
+ const node = getStateTreeNode(target);
657
+ return node.volatileState.$frozen === true;
658
+ }
659
+ function unfreeze(target) {
660
+ const node = getStateTreeNode(target);
661
+ delete node.volatileState.$frozen;
662
+ for (const [, child] of node.getChildren()) {
663
+ const instance = child.getInstance();
664
+ if (instance && hasStateTreeNode(instance)) {
665
+ unfreeze(instance);
666
+ }
667
+ }
668
+ }
669
+ var import_jotai, globalStore, nodeRegistry, nodeFinalizationRegistry, identifierRegistry, identifierFinalizationRegistry, nodeIdCounter, lifecycleListeners, StateTreeNode, $treenode, currentAction, actionListeners, actionRecorderHooks;
670
+ var init_tree = __esm({
671
+ "src/tree.ts"() {
672
+ "use strict";
673
+ import_jotai = require("jotai");
674
+ globalStore = (0, import_jotai.createStore)();
675
+ nodeRegistry = /* @__PURE__ */ new Map();
676
+ nodeFinalizationRegistry = new FinalizationRegistry((nodeId) => {
677
+ nodeRegistry.delete(nodeId);
678
+ });
679
+ identifierRegistry = /* @__PURE__ */ new Map();
680
+ identifierFinalizationRegistry = new FinalizationRegistry(
681
+ (info) => {
682
+ const typeMap = identifierRegistry.get(info.typeName);
683
+ if (typeMap) {
684
+ typeMap.delete(info.identifier);
685
+ if (typeMap.size === 0) {
686
+ identifierRegistry.delete(info.typeName);
687
+ }
688
+ }
689
+ }
690
+ );
691
+ nodeIdCounter = 0;
692
+ lifecycleListeners = /* @__PURE__ */ new WeakMap();
693
+ StateTreeNode = class {
694
+ constructor(type, initialValue, env, parent, pathSegment) {
695
+ this.$parent = null;
696
+ this.$path = "";
697
+ this.$isAlive = true;
698
+ /** Child nodes - uses Map but children are explicitly destroyed */
699
+ this.children = /* @__PURE__ */ new Map();
700
+ /** Snapshot listeners */
701
+ this.snapshotListeners = /* @__PURE__ */ new Set();
702
+ /** Patch listeners */
703
+ this.patchListeners = /* @__PURE__ */ new Set();
704
+ /** Volatile state (non-serialized) */
705
+ this.volatileState = {};
706
+ this.$id = generateNodeId();
707
+ this.$type = type;
708
+ this.$env = env ?? parent?.$env;
709
+ this.$parent = parent ?? null;
710
+ this.$path = parent ? `${parent.$path}/${pathSegment}` : "";
711
+ this.valueAtom = (0, import_jotai.atom)(initialValue);
712
+ nodeRegistry.set(this.$id, { node: new WeakRef(this), instance: null });
713
+ nodeFinalizationRegistry.register(this, this.$id, this);
714
+ }
715
+ /** Set the instance reference */
716
+ setInstance(instance) {
717
+ const entry = nodeRegistry.get(this.$id);
718
+ if (entry && instance && typeof instance === "object") {
719
+ entry.instance = new WeakRef(instance);
720
+ }
721
+ }
722
+ /** Get the instance */
723
+ getInstance() {
724
+ const entry = nodeRegistry.get(this.$id);
725
+ return entry?.instance?.deref() ?? null;
726
+ }
727
+ /** Get current value from atom */
728
+ getValue() {
729
+ return globalStore.get(this.valueAtom);
730
+ }
731
+ /** Set value on atom */
732
+ setValue(value) {
733
+ if (!this.$isAlive) {
734
+ throw new Error(
735
+ `[jotai-state-tree] Cannot modify a node that is no longer part of the state tree. (Node type: '${this.$type.name}', Path: '${this.$path}')`
736
+ );
737
+ }
738
+ const oldValue = this.getValue();
739
+ globalStore.set(this.valueAtom, value);
740
+ this.notifyPatch(
741
+ { op: "replace", path: this.$path, value },
742
+ { op: "replace", path: this.$path, value: oldValue, oldValue }
743
+ );
744
+ this.notifySnapshotChange();
745
+ }
746
+ /** Add a child node */
747
+ addChild(key, child) {
748
+ child.$parent = this;
749
+ const newPath = `${this.$path}/${key}`;
750
+ this.updatePathRecursively(child, newPath);
751
+ child.$env = child.$env ?? this.$env;
752
+ this.children.set(key, child);
753
+ }
754
+ /** Recursively update the path of a node and all its children */
755
+ updatePathRecursively(node, newPath) {
756
+ node.$path = newPath;
757
+ for (const [childKey, childNode] of node.children) {
758
+ const childNewPath = `${newPath}/${childKey}`;
759
+ this.updatePathRecursively(childNode, childNewPath);
760
+ }
761
+ }
762
+ /** Remove a child node */
763
+ removeChild(key) {
764
+ const child = this.children.get(key);
765
+ if (child) {
766
+ child.destroy();
767
+ this.children.delete(key);
768
+ }
769
+ }
770
+ /** Get a child node */
771
+ getChild(key) {
772
+ return this.children.get(key);
773
+ }
774
+ /** Get all children */
775
+ getChildren() {
776
+ return this.children;
777
+ }
778
+ /** Register identifier */
779
+ registerIdentifier(typeName, identifier) {
780
+ this.identifierTypeName = typeName;
781
+ this.identifierValue = identifier;
782
+ let typeMap = identifierRegistry.get(typeName);
783
+ if (!typeMap) {
784
+ typeMap = /* @__PURE__ */ new Map();
785
+ identifierRegistry.set(typeName, typeMap);
786
+ }
787
+ typeMap.set(identifier, new WeakRef(this));
788
+ identifierFinalizationRegistry.register(
789
+ this,
790
+ { typeName, identifier },
791
+ this
792
+ );
793
+ }
794
+ /** Unregister identifier */
795
+ unregisterIdentifier() {
796
+ if (this.identifierTypeName !== void 0 && this.identifierValue !== void 0) {
797
+ const typeMap = identifierRegistry.get(this.identifierTypeName);
798
+ if (typeMap) {
799
+ typeMap.delete(this.identifierValue);
800
+ if (typeMap.size === 0) {
801
+ identifierRegistry.delete(this.identifierTypeName);
802
+ }
803
+ }
804
+ identifierFinalizationRegistry.unregister(this);
805
+ }
806
+ }
807
+ /** Subscribe to snapshot changes */
808
+ onSnapshot(listener) {
809
+ this.snapshotListeners.add(listener);
810
+ return () => {
811
+ this.snapshotListeners.delete(listener);
812
+ };
813
+ }
814
+ /** Subscribe to patches */
815
+ onPatch(listener) {
816
+ this.patchListeners.add(listener);
817
+ return () => {
818
+ this.patchListeners.delete(listener);
819
+ };
820
+ }
821
+ /** Notify patch listeners */
822
+ notifyPatch(patch, reversePatch) {
823
+ this.patchListeners.forEach((listener) => listener(patch, reversePatch));
824
+ if (this.$parent) {
825
+ this.$parent.notifyPatch(patch, reversePatch);
826
+ }
827
+ }
828
+ /** Notify snapshot listeners */
829
+ notifySnapshotChange() {
830
+ const root = this.getRoot();
831
+ const snapshot = getSnapshotFromNode(root);
832
+ root.snapshotListeners.forEach((listener) => listener(snapshot));
833
+ }
834
+ /** Notify about a property change (for use by model proxy) */
835
+ notifyPropertyChange(propName, newValue, oldValue) {
836
+ const path = this.$path ? `${this.$path}/${propName}` : `/${propName}`;
837
+ this.notifyPatch(
838
+ { op: "replace", path, value: newValue },
839
+ { op: "replace", path, value: oldValue, oldValue }
840
+ );
841
+ this.notifySnapshotChange();
842
+ }
843
+ /** Get root node */
844
+ getRoot() {
845
+ let node = this;
846
+ while (node.$parent) {
847
+ node = node.$parent;
848
+ }
849
+ return node;
850
+ }
851
+ /** Destroy this node and all children */
852
+ destroy() {
853
+ if (!this.$isAlive) return;
854
+ this.children.forEach((child) => child.destroy());
855
+ this.children.clear();
856
+ this.unregisterIdentifier();
857
+ this.$isAlive = false;
858
+ notifyLifecycleChange(this, false);
859
+ nodeRegistry.delete(this.$id);
860
+ nodeFinalizationRegistry.unregister(this);
861
+ this.snapshotListeners.clear();
862
+ this.patchListeners.clear();
863
+ }
864
+ /** Detach from parent */
865
+ detach() {
866
+ if (this.$parent) {
867
+ for (const [key, child] of this.$parent.children) {
868
+ if (child === this) {
869
+ this.$parent.children.delete(key);
870
+ break;
871
+ }
872
+ }
873
+ this.$parent = null;
874
+ this.$path = "";
875
+ }
876
+ }
877
+ };
878
+ $treenode = /* @__PURE__ */ Symbol.for("jotai-state-tree-node");
879
+ currentAction = null;
880
+ actionListeners = /* @__PURE__ */ new Set();
881
+ actionRecorderHooks = [];
882
+ }
883
+ });
884
+
885
+ // src/react.ts
886
+ var react_exports = {};
887
+ __export(react_exports, {
888
+ Observer: () => Observer,
889
+ Provider: () => Provider,
890
+ batch: () => batch,
891
+ createStoreContext: () => createStoreContext,
892
+ observer: () => observer,
893
+ scheduleUpdate: () => scheduleUpdate,
894
+ useAction: () => useAction,
895
+ useActions: () => useActions,
896
+ useCleanup: () => useCleanup,
897
+ useIsAlive: () => useIsAlive,
898
+ useLocalObservable: () => useLocalObservable,
899
+ useObserver: () => useObserver,
900
+ usePatches: () => usePatches,
901
+ useSnapshot: () => useSnapshot,
902
+ useStore: () => useStore,
903
+ useStoreSnapshot: () => useStoreSnapshot,
904
+ useSyncedStore: () => useSyncedStore,
905
+ useWatchPath: () => useWatchPath
906
+ });
907
+ module.exports = __toCommonJS(react_exports);
908
+ var import_react = __toESM(require("react"));
909
+ init_tree();
910
+ function observer(Component, options) {
911
+ const displayName = Component.displayName || Component.name || "Component";
912
+ const ObserverComponent = (0, import_react.memo)((props) => {
913
+ const [, forceUpdate] = (0, import_react.useState)({});
914
+ const disposersRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
915
+ const trackedNodesRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
916
+ const trackNode = (node) => {
917
+ if (hasStateTreeNode(node) && !trackedNodesRef.current.has(node)) {
918
+ trackedNodesRef.current.add(node);
919
+ const disposer = onSnapshot(node, () => {
920
+ forceUpdate({});
921
+ });
922
+ disposersRef.current.add(disposer);
923
+ }
924
+ };
925
+ const createTrackingProxy = (target) => {
926
+ if (!target || typeof target !== "object") return target;
927
+ if (hasStateTreeNode(target)) {
928
+ trackNode(target);
929
+ }
930
+ return new Proxy(target, {
931
+ get(obj, prop) {
932
+ const value = obj[prop];
933
+ if (value && typeof value === "object" && hasStateTreeNode(value)) {
934
+ trackNode(value);
935
+ return createTrackingProxy(value);
936
+ }
937
+ return value;
938
+ }
939
+ });
940
+ };
941
+ (0, import_react.useEffect)(() => {
942
+ return () => {
943
+ disposersRef.current.forEach((d) => d());
944
+ disposersRef.current.clear();
945
+ trackedNodesRef.current.clear();
946
+ };
947
+ }, []);
948
+ const trackedProps = (0, import_react.useMemo)(() => {
949
+ const tracked = {};
950
+ for (const [key, value] of Object.entries(props)) {
951
+ if (value && typeof value === "object") {
952
+ tracked[key] = createTrackingProxy(value);
953
+ } else {
954
+ tracked[key] = value;
955
+ }
956
+ }
957
+ return tracked;
958
+ }, [props]);
959
+ return import_react.default.createElement(Component, trackedProps);
960
+ });
961
+ ObserverComponent.displayName = `Observer(${displayName})`;
962
+ if (options?.forwardRef) {
963
+ const ForwardedComponent = (0, import_react.forwardRef)((props, ref) => {
964
+ const propsWithRef = Object.assign({}, props, { ref });
965
+ return import_react.default.createElement(
966
+ ObserverComponent,
967
+ propsWithRef
968
+ );
969
+ });
970
+ ForwardedComponent.displayName = `ForwardRef(${displayName})`;
971
+ return ForwardedComponent;
972
+ }
973
+ return ObserverComponent;
974
+ }
975
+ var Observer = observer(({ children }) => {
976
+ return import_react.default.createElement(import_react.default.Fragment, null, children());
977
+ });
978
+ function useObserver(fn) {
979
+ const [, forceUpdate] = (0, import_react.useState)({});
980
+ const disposersRef = (0, import_react.useRef)([]);
981
+ const trackedNodes = (0, import_react.useRef)(/* @__PURE__ */ new Set());
982
+ (0, import_react.useEffect)(() => {
983
+ return () => {
984
+ disposersRef.current.forEach((d) => d());
985
+ disposersRef.current = [];
986
+ };
987
+ }, []);
988
+ const result = (0, import_react.useMemo)(() => {
989
+ trackedNodes.current.clear();
990
+ disposersRef.current.forEach((d) => d());
991
+ disposersRef.current = [];
992
+ const value = fn();
993
+ return value;
994
+ }, [fn]);
995
+ return result;
996
+ }
997
+ function useLocalObservable(initializer, dependencies = []) {
998
+ const [, forceUpdate] = (0, import_react.useState)({});
999
+ const storeRef = (0, import_react.useRef)(null);
1000
+ const disposerRef = (0, import_react.useRef)(null);
1001
+ if (storeRef.current === null) {
1002
+ storeRef.current = initializer();
1003
+ if (hasStateTreeNode(storeRef.current)) {
1004
+ disposerRef.current = onSnapshot(storeRef.current, () => {
1005
+ forceUpdate({});
1006
+ });
1007
+ }
1008
+ }
1009
+ (0, import_react.useEffect)(() => {
1010
+ return () => {
1011
+ disposerRef.current?.();
1012
+ };
1013
+ }, []);
1014
+ (0, import_react.useEffect)(() => {
1015
+ if (dependencies.length > 0) {
1016
+ disposerRef.current?.();
1017
+ storeRef.current = initializer();
1018
+ if (hasStateTreeNode(storeRef.current)) {
1019
+ disposerRef.current = onSnapshot(storeRef.current, () => {
1020
+ forceUpdate({});
1021
+ });
1022
+ }
1023
+ }
1024
+ }, dependencies);
1025
+ return storeRef.current;
1026
+ }
1027
+ function useSyncedStore(store) {
1028
+ const snapshotRef = (0, import_react.useRef)(null);
1029
+ const subscribe = (0, import_react.useCallback)(
1030
+ (callback) => {
1031
+ if (!hasStateTreeNode(store)) {
1032
+ return () => {
1033
+ };
1034
+ }
1035
+ return onSnapshot(store, () => {
1036
+ snapshotRef.current = getSnapshot(store);
1037
+ callback();
1038
+ });
1039
+ },
1040
+ [store]
1041
+ );
1042
+ const getSnapshotValue = (0, import_react.useCallback)(() => {
1043
+ if (!hasStateTreeNode(store)) {
1044
+ return null;
1045
+ }
1046
+ if (snapshotRef.current === null) {
1047
+ snapshotRef.current = getSnapshot(store);
1048
+ }
1049
+ return snapshotRef.current;
1050
+ }, [store]);
1051
+ (0, import_react.useSyncExternalStore)(subscribe, getSnapshotValue, getSnapshotValue);
1052
+ return store;
1053
+ }
1054
+ var StoreContext = import_react.default.createContext(
1055
+ null
1056
+ );
1057
+ function Provider({
1058
+ store,
1059
+ children
1060
+ }) {
1061
+ const value = (0, import_react.useMemo)(() => ({ store }), [store]);
1062
+ return import_react.default.createElement(StoreContext.Provider, { value }, children);
1063
+ }
1064
+ function useStore() {
1065
+ const context = import_react.default.useContext(StoreContext);
1066
+ if (!context) {
1067
+ throw new Error(
1068
+ "[jotai-state-tree] useStore must be used within a Provider"
1069
+ );
1070
+ }
1071
+ return context.store;
1072
+ }
1073
+ function useStoreSnapshot(selector) {
1074
+ const store = useStore();
1075
+ const [, forceUpdate] = (0, import_react.useState)({});
1076
+ (0, import_react.useEffect)(() => {
1077
+ if (hasStateTreeNode(store)) {
1078
+ return onSnapshot(store, () => {
1079
+ forceUpdate({});
1080
+ });
1081
+ }
1082
+ return () => {
1083
+ };
1084
+ }, [store]);
1085
+ if (selector) {
1086
+ return selector(store);
1087
+ }
1088
+ return store;
1089
+ }
1090
+ function createStoreContext() {
1091
+ const Context = import_react.default.createContext(null);
1092
+ function StoreProvider({
1093
+ store,
1094
+ children
1095
+ }) {
1096
+ return import_react.default.createElement(Context.Provider, { value: store }, children);
1097
+ }
1098
+ function useTypedStore() {
1099
+ const store = import_react.default.useContext(Context);
1100
+ if (store === null) {
1101
+ throw new Error(
1102
+ "[jotai-state-tree] useStore must be used within a Provider"
1103
+ );
1104
+ }
1105
+ return store;
1106
+ }
1107
+ function useTypedStoreSnapshot(selector) {
1108
+ const store = useTypedStore();
1109
+ const [, forceUpdate] = (0, import_react.useState)({});
1110
+ (0, import_react.useEffect)(() => {
1111
+ if (hasStateTreeNode(store)) {
1112
+ return onSnapshot(store, () => {
1113
+ forceUpdate({});
1114
+ });
1115
+ }
1116
+ return () => {
1117
+ };
1118
+ }, [store]);
1119
+ if (selector) {
1120
+ return selector(store);
1121
+ }
1122
+ return store;
1123
+ }
1124
+ function useTypedIsAlive() {
1125
+ const store = useTypedStore();
1126
+ return useIsAlive(store);
1127
+ }
1128
+ return {
1129
+ Provider: StoreProvider,
1130
+ useStore: useTypedStore,
1131
+ useStoreSnapshot: useTypedStoreSnapshot,
1132
+ useIsAlive: useTypedIsAlive,
1133
+ Context
1134
+ };
1135
+ }
1136
+ function useSnapshot(target) {
1137
+ const [snapshot, setSnapshot] = (0, import_react.useState)(() => getSnapshot(target));
1138
+ (0, import_react.useEffect)(() => {
1139
+ const disposer = onSnapshot(target, (newSnapshot) => {
1140
+ setSnapshot(newSnapshot);
1141
+ });
1142
+ return disposer;
1143
+ }, [target]);
1144
+ return snapshot;
1145
+ }
1146
+ function useWatchPath(target, path, defaultValue) {
1147
+ const [value, setValue] = (0, import_react.useState)(() => {
1148
+ const snapshot = getSnapshot(target);
1149
+ const parts = path.split(".");
1150
+ let current = snapshot;
1151
+ for (const part of parts) {
1152
+ if (current && typeof current === "object" && part in current) {
1153
+ current = current[part];
1154
+ } else {
1155
+ return defaultValue;
1156
+ }
1157
+ }
1158
+ return current;
1159
+ });
1160
+ (0, import_react.useEffect)(() => {
1161
+ const disposer = onSnapshot(target, (newSnapshot) => {
1162
+ const snapshot = newSnapshot;
1163
+ const parts = path.split(".");
1164
+ let current = snapshot;
1165
+ for (const part of parts) {
1166
+ if (current && typeof current === "object" && part in current) {
1167
+ current = current[part];
1168
+ } else {
1169
+ setValue(defaultValue);
1170
+ return;
1171
+ }
1172
+ }
1173
+ setValue(current);
1174
+ });
1175
+ return disposer;
1176
+ }, [target, path, defaultValue]);
1177
+ return value;
1178
+ }
1179
+ function usePatches(target, callback) {
1180
+ (0, import_react.useEffect)(() => {
1181
+ const { onPatch: onPatch2 } = (init_tree(), __toCommonJS(tree_exports));
1182
+ const disposer = onPatch2(target, callback);
1183
+ return disposer;
1184
+ }, [target, callback]);
1185
+ }
1186
+ function useAction(action) {
1187
+ return (0, import_react.useMemo)(() => action, [action]);
1188
+ }
1189
+ function useActions(actions) {
1190
+ return (0, import_react.useMemo)(() => actions, [actions]);
1191
+ }
1192
+ var batchDepth = 0;
1193
+ var pendingUpdates = /* @__PURE__ */ new Set();
1194
+ function batch(fn) {
1195
+ batchDepth++;
1196
+ try {
1197
+ fn();
1198
+ } finally {
1199
+ batchDepth--;
1200
+ if (batchDepth === 0 && pendingUpdates.size > 0) {
1201
+ const updates = pendingUpdates;
1202
+ pendingUpdates = /* @__PURE__ */ new Set();
1203
+ updates.forEach((update) => update());
1204
+ }
1205
+ }
1206
+ }
1207
+ function scheduleUpdate(update) {
1208
+ if (batchDepth > 0) {
1209
+ pendingUpdates.add(update);
1210
+ } else {
1211
+ update();
1212
+ }
1213
+ }
1214
+ function useIsAlive(target) {
1215
+ const [isAlive2, setIsAlive] = (0, import_react.useState)(() => {
1216
+ if (!hasStateTreeNode(target)) return false;
1217
+ return getStateTreeNode(target).$isAlive;
1218
+ });
1219
+ (0, import_react.useEffect)(() => {
1220
+ if (!hasStateTreeNode(target)) return;
1221
+ const node = getStateTreeNode(target);
1222
+ setIsAlive(node.$isAlive);
1223
+ const disposer = onLifecycleChange(node, (alive) => {
1224
+ setIsAlive(alive);
1225
+ });
1226
+ return disposer;
1227
+ }, [target]);
1228
+ return isAlive2;
1229
+ }
1230
+ function useCleanup(cleanupFn) {
1231
+ const cleanupRef = (0, import_react.useRef)(cleanupFn);
1232
+ cleanupRef.current = cleanupFn;
1233
+ (0, import_react.useEffect)(() => {
1234
+ return () => {
1235
+ cleanupRef.current();
1236
+ };
1237
+ }, []);
1238
+ }
1239
+ // Annotate the CommonJS export names for ESM import in node:
1240
+ 0 && (module.exports = {
1241
+ Observer,
1242
+ Provider,
1243
+ batch,
1244
+ createStoreContext,
1245
+ observer,
1246
+ scheduleUpdate,
1247
+ useAction,
1248
+ useActions,
1249
+ useCleanup,
1250
+ useIsAlive,
1251
+ useLocalObservable,
1252
+ useObserver,
1253
+ usePatches,
1254
+ useSnapshot,
1255
+ useStore,
1256
+ useStoreSnapshot,
1257
+ useSyncedStore,
1258
+ useWatchPath
1259
+ });