@graphrefly/graphrefly 0.6.0 → 0.8.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.
Files changed (75) hide show
  1. package/README.md +30 -14
  2. package/dist/{chunk-HP7OKEOE.js → chunk-A2AJJOSJ.js} +3 -3
  3. package/dist/chunk-A2AJJOSJ.js.map +1 -0
  4. package/dist/{chunk-CP6MNKAA.js → chunk-E7OH6ZAZ.js} +10 -4
  5. package/dist/{chunk-CP6MNKAA.js.map → chunk-E7OH6ZAZ.js.map} +1 -1
  6. package/dist/chunk-LR2CLSEF.js +106 -0
  7. package/dist/chunk-LR2CLSEF.js.map +1 -0
  8. package/dist/{chunk-5X3LAO3B.js → chunk-QTZSBQGJ.js} +79 -20
  9. package/dist/chunk-QTZSBQGJ.js.map +1 -0
  10. package/dist/{chunk-V3UACY6A.js → chunk-TZLX4KIT.js} +790 -203
  11. package/dist/chunk-TZLX4KIT.js.map +1 -0
  12. package/dist/{chunk-QW7H3ICI.js → chunk-UCW3VWMN.js} +4 -4
  13. package/dist/{chunk-6W5SGIGB.js → chunk-WYI7YW54.js} +142 -30
  14. package/dist/chunk-WYI7YW54.js.map +1 -0
  15. package/dist/chunk-WZ2Z2CRV.js +32 -0
  16. package/dist/chunk-WZ2Z2CRV.js.map +1 -0
  17. package/dist/{chunk-Z4Y4FMQN.js → chunk-XCZPGOVP.js} +7 -7
  18. package/dist/{chunk-KWXPDASV.js → chunk-YWTP2XRJ.js} +2 -2
  19. package/dist/compat/nestjs/index.cjs +268 -61
  20. package/dist/compat/nestjs/index.cjs.map +1 -1
  21. package/dist/compat/nestjs/index.d.cts +4 -4
  22. package/dist/compat/nestjs/index.d.ts +4 -4
  23. package/dist/compat/nestjs/index.js +8 -7
  24. package/dist/core/index.cjs +163 -35
  25. package/dist/core/index.cjs.map +1 -1
  26. package/dist/core/index.d.cts +2 -2
  27. package/dist/core/index.d.ts +2 -2
  28. package/dist/core/index.js +10 -4
  29. package/dist/extra/index.cjs +892 -221
  30. package/dist/extra/index.cjs.map +1 -1
  31. package/dist/extra/index.d.cts +4 -4
  32. package/dist/extra/index.d.ts +4 -4
  33. package/dist/extra/index.js +22 -3
  34. package/dist/graph/index.cjs +268 -61
  35. package/dist/graph/index.cjs.map +1 -1
  36. package/dist/graph/index.d.cts +3 -3
  37. package/dist/graph/index.d.ts +3 -3
  38. package/dist/graph/index.js +4 -4
  39. package/dist/{graph-CL_ZDAj9.d.cts → graph-DqTICAY2.d.cts} +69 -12
  40. package/dist/{graph-D18qmsNm.d.ts → graph-X9uwnD_z.d.ts} +69 -12
  41. package/dist/{index-C3BMRmmp.d.cts → index-3U0WxdD-.d.cts} +3 -3
  42. package/dist/{index-Bk_idZm1.d.cts → index-BP1t_38S.d.cts} +406 -61
  43. package/dist/{index-BtK55IE2.d.ts → index-BPCeYDS4.d.ts} +4 -2
  44. package/dist/{index-Bvy_6CaN.d.ts → index-BVG5pjin.d.ts} +50 -5
  45. package/dist/{index-C5mqLhMX.d.cts → index-BYEgosAX.d.cts} +50 -5
  46. package/dist/{index-D_geH2Bm.d.cts → index-BYa2YMat.d.cts} +3 -3
  47. package/dist/{index-CP_QvbWu.d.ts → index-DLO8wnYU.d.ts} +3 -3
  48. package/dist/{index-B7eOdgEx.d.ts → index-DMv1Etbi.d.ts} +3 -3
  49. package/dist/{index-BvhgZRHK.d.cts → index-DbwgQ4Cw.d.cts} +4 -2
  50. package/dist/{index-B2jmzVxL.d.ts → index-a5gHmH5b.d.ts} +406 -61
  51. package/dist/index.cjs +2966 -1790
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.cts +112 -14
  54. package/dist/index.d.ts +112 -14
  55. package/dist/index.js +385 -20
  56. package/dist/index.js.map +1 -1
  57. package/dist/{meta-BsF6Sag9.d.cts → meta-BJEU8fYz.d.cts} +31 -4
  58. package/dist/{meta-BsF6Sag9.d.ts → meta-BJEU8fYz.d.ts} +31 -4
  59. package/dist/patterns/reactive-layout/index.cjs +268 -61
  60. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  61. package/dist/patterns/reactive-layout/index.d.cts +3 -3
  62. package/dist/patterns/reactive-layout/index.d.ts +3 -3
  63. package/dist/patterns/reactive-layout/index.js +4 -4
  64. package/dist/{reactive-log-BfvfNWQh.d.cts → reactive-log-BfX6bOSZ.d.cts} +2 -2
  65. package/dist/{reactive-log-ohLmTXoZ.d.ts → reactive-log-RhgIog2Z.d.ts} +2 -2
  66. package/package.json +29 -18
  67. package/dist/chunk-5X3LAO3B.js.map +0 -1
  68. package/dist/chunk-6W5SGIGB.js.map +0 -1
  69. package/dist/chunk-HP7OKEOE.js.map +0 -1
  70. package/dist/chunk-O3PI7W45.js +0 -68
  71. package/dist/chunk-O3PI7W45.js.map +0 -1
  72. package/dist/chunk-V3UACY6A.js.map +0 -1
  73. /package/dist/{chunk-QW7H3ICI.js.map → chunk-UCW3VWMN.js.map} +0 -0
  74. /package/dist/{chunk-Z4Y4FMQN.js.map → chunk-XCZPGOVP.js.map} +0 -0
  75. /package/dist/{chunk-KWXPDASV.js.map → chunk-YWTP2XRJ.js.map} +0 -0
@@ -447,10 +447,14 @@ function partitionForBatch(messages) {
447
447
  }
448
448
  return { immediate, deferred, terminal };
449
449
  }
450
- function emitWithBatch(emit, messages, phase = 2) {
450
+ function emitWithBatch(emit, messages, phase = 2, options) {
451
451
  if (messages.length === 0) {
452
452
  return;
453
453
  }
454
+ if (options?.strategy === "sequential") {
455
+ _emitSequential(emit, messages, phase);
456
+ return;
457
+ }
454
458
  const queue = phase === 3 ? pendingPhase3 : pendingPhase2;
455
459
  if (messages.length === 1) {
456
460
  const t = messages[0][0];
@@ -491,6 +495,29 @@ function emitWithBatch(emit, messages, phase = 2) {
491
495
  }
492
496
  }
493
497
  }
498
+ function _emitSequential(emit, messages, phase = 2) {
499
+ const dataQueue = phase === 3 ? pendingPhase3 : pendingPhase2;
500
+ for (const msg of messages) {
501
+ const tier = messageTier(msg[0]);
502
+ if (tier === 2) {
503
+ if (isBatching()) {
504
+ const m = msg;
505
+ dataQueue.push(() => emit([m]));
506
+ } else {
507
+ emit([msg]);
508
+ }
509
+ } else if (tier >= 3) {
510
+ if (isBatching()) {
511
+ const m = msg;
512
+ pendingPhase3.push(() => emit([m]));
513
+ } else {
514
+ emit([msg]);
515
+ }
516
+ } else {
517
+ emit([msg]);
518
+ }
519
+ }
520
+ }
494
521
 
495
522
  // src/core/guard.ts
496
523
  var GuardDenied = class extends Error {
@@ -621,6 +648,7 @@ function advanceVersion(info, newValue, hashFn) {
621
648
  }
622
649
 
623
650
  // src/core/node.ts
651
+ var NO_VALUE = /* @__PURE__ */ Symbol.for("graphrefly/NO_VALUE");
624
652
  function createIntBitSet() {
625
653
  let bits = 0;
626
654
  return {
@@ -750,10 +778,10 @@ var NodeImpl = class {
750
778
  this._hasDeps = deps.length > 0;
751
779
  this._autoComplete = opts.completeWhenDepsComplete ?? true;
752
780
  this._isSingleDep = deps.length === 1 && fn != null;
753
- this._cached = opts.initial;
781
+ this._cached = "initial" in opts ? opts.initial : NO_VALUE;
754
782
  this._status = this._hasDeps ? "disconnected" : "settled";
755
783
  this._hashFn = opts.versioningHash ?? defaultHash;
756
- this._versioning = opts.versioning != null ? createVersioning(opts.versioning, this._cached, {
784
+ this._versioning = opts.versioning != null ? createVersioning(opts.versioning, this._cached === NO_VALUE ? void 0 : this._cached, {
757
785
  id: opts.versioningId,
758
786
  hash: this._hashFn
759
787
  }) : void 0;
@@ -837,10 +865,14 @@ var NodeImpl = class {
837
865
  _applyVersioning(level, opts) {
838
866
  if (this._versioning != null) return;
839
867
  this._hashFn = opts?.hash ?? this._hashFn;
840
- this._versioning = createVersioning(level, this._cached, {
841
- id: opts?.id,
842
- hash: this._hashFn
843
- });
868
+ this._versioning = createVersioning(
869
+ level,
870
+ this._cached === NO_VALUE ? void 0 : this._cached,
871
+ {
872
+ id: opts?.id,
873
+ hash: this._hashFn
874
+ }
875
+ );
844
876
  }
845
877
  hasGuard() {
846
878
  return this._guard != null;
@@ -850,7 +882,7 @@ var NodeImpl = class {
850
882
  return this._guard(normalizeActor(actor), "observe");
851
883
  }
852
884
  get() {
853
- return this._cached;
885
+ return this._cached === NO_VALUE ? void 0 : this._cached;
854
886
  }
855
887
  down(messages, options) {
856
888
  if (messages.length === 0) return;
@@ -907,6 +939,7 @@ var NodeImpl = class {
907
939
  }
908
940
  if (this._terminal && this._opts.resubscribable) {
909
941
  this._terminal = false;
942
+ this._cached = NO_VALUE;
910
943
  this._status = this._hasDeps ? "disconnected" : "settled";
911
944
  this._opts.onResubscribe?.();
912
945
  }
@@ -1006,7 +1039,7 @@ var NodeImpl = class {
1006
1039
  const cleanupFn = this._cleanup;
1007
1040
  this._cleanup = void 0;
1008
1041
  cleanupFn?.();
1009
- this._cached = void 0;
1042
+ this._cached = NO_VALUE;
1010
1043
  this._lastDepValues = void 0;
1011
1044
  }
1012
1045
  this._status = statusAfterMessage(this._status, m);
@@ -1015,7 +1048,7 @@ var NodeImpl = class {
1015
1048
  }
1016
1049
  if (t === TEARDOWN) {
1017
1050
  if (this._opts.resetOnTeardown) {
1018
- this._cached = void 0;
1051
+ this._cached = NO_VALUE;
1019
1052
  }
1020
1053
  const teardownCleanup = this._cleanup;
1021
1054
  this._cleanup = void 0;
@@ -1046,7 +1079,15 @@ var NodeImpl = class {
1046
1079
  }
1047
1080
  _emitAutoValue(value) {
1048
1081
  const wasDirty = this._status === "dirty";
1049
- const unchanged = this._equals(this._cached, value);
1082
+ let unchanged;
1083
+ try {
1084
+ unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
1085
+ } catch (eqErr) {
1086
+ const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
1087
+ const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, { cause: eqErr });
1088
+ this._downInternal([[ERROR, wrapped]]);
1089
+ return;
1090
+ }
1050
1091
  if (unchanged) {
1051
1092
  this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
1052
1093
  return;
@@ -1093,7 +1134,9 @@ var NodeImpl = class {
1093
1134
  if (out === void 0) return;
1094
1135
  this._emitAutoValue(out);
1095
1136
  } catch (err) {
1096
- this._downInternal([[ERROR, err]]);
1137
+ const errMsg = err instanceof Error ? err.message : String(err);
1138
+ const wrapped = new Error(`Node "${this.name}": fn threw: ${errMsg}`, { cause: err });
1139
+ this._downInternal([[ERROR, wrapped]]);
1097
1140
  }
1098
1141
  }
1099
1142
  _onDepDirty(index) {
@@ -1128,7 +1171,11 @@ var NodeImpl = class {
1128
1171
  try {
1129
1172
  if (this._onMessage(msg, index, this._actions)) continue;
1130
1173
  } catch (err) {
1131
- this._downInternal([[ERROR, err]]);
1174
+ const errMsg = err instanceof Error ? err.message : String(err);
1175
+ const wrapped = new Error(`Node "${this.name}": onMessage threw: ${errMsg}`, {
1176
+ cause: err
1177
+ });
1178
+ this._downInternal([[ERROR, wrapped]]);
1132
1179
  return;
1133
1180
  }
1134
1181
  }
@@ -2095,7 +2142,7 @@ var DynamicNodeImpl = class {
2095
2142
  _actions;
2096
2143
  _boundEmitToSinks;
2097
2144
  // Mutable state
2098
- _cached;
2145
+ _cached = NO_VALUE;
2099
2146
  _status = "disconnected";
2100
2147
  _terminal = false;
2101
2148
  _connected = false;
@@ -2189,7 +2236,7 @@ var DynamicNodeImpl = class {
2189
2236
  return this._guard(normalizeActor(actor), "observe");
2190
2237
  }
2191
2238
  get() {
2192
- return this._cached;
2239
+ return this._cached === NO_VALUE ? void 0 : this._cached;
2193
2240
  }
2194
2241
  down(messages, options) {
2195
2242
  if (messages.length === 0) return;
@@ -2247,6 +2294,7 @@ var DynamicNodeImpl = class {
2247
2294
  }
2248
2295
  if (this._terminal && this._resubscribable) {
2249
2296
  this._terminal = false;
2297
+ this._cached = NO_VALUE;
2250
2298
  this._status = "disconnected";
2251
2299
  this._onResubscribe?.();
2252
2300
  }
@@ -2324,10 +2372,13 @@ var DynamicNodeImpl = class {
2324
2372
  const t = m[0];
2325
2373
  if (t === DATA) this._cached = m[1];
2326
2374
  if (t === INVALIDATE) {
2327
- this._cached = void 0;
2375
+ this._cached = NO_VALUE;
2376
+ this._status = "dirty";
2328
2377
  }
2329
- if (t === DATA || t === RESOLVED) {
2378
+ if (t === DATA) {
2330
2379
  this._status = "settled";
2380
+ } else if (t === RESOLVED) {
2381
+ this._status = "resolved";
2331
2382
  } else if (t === DIRTY) {
2332
2383
  this._status = "dirty";
2333
2384
  } else if (t === COMPLETE) {
@@ -2338,7 +2389,7 @@ var DynamicNodeImpl = class {
2338
2389
  this._terminal = true;
2339
2390
  }
2340
2391
  if (t === TEARDOWN) {
2341
- if (this._resetOnTeardown) this._cached = void 0;
2392
+ if (this._resetOnTeardown) this._cached = NO_VALUE;
2342
2393
  try {
2343
2394
  this._propagateToMeta(t);
2344
2395
  } finally {
@@ -2361,7 +2412,15 @@ var DynamicNodeImpl = class {
2361
2412
  }
2362
2413
  _emitAutoValue(value) {
2363
2414
  const wasDirty = this._status === "dirty";
2364
- const unchanged = this._equals(this._cached, value);
2415
+ let unchanged;
2416
+ try {
2417
+ unchanged = this._cached !== NO_VALUE && this._equals(this._cached, value);
2418
+ } catch (eqErr) {
2419
+ const eqMsg = eqErr instanceof Error ? eqErr.message : String(eqErr);
2420
+ const wrapped = new Error(`Node "${this.name}": equals threw: ${eqMsg}`, { cause: eqErr });
2421
+ this._downInternal([[ERROR, wrapped]]);
2422
+ return;
2423
+ }
2365
2424
  if (unchanged) {
2366
2425
  this._downInternal(wasDirty ? [[RESOLVED]] : [[DIRTY], [RESOLVED]]);
2367
2426
  return;
@@ -2527,6 +2586,19 @@ var DynamicNodeImpl = class {
2527
2586
  };
2528
2587
 
2529
2588
  // src/core/meta.ts
2589
+ function resolveDescribeFields(detail, fields) {
2590
+ if (fields != null && fields.length > 0) return new Set(fields);
2591
+ switch (detail) {
2592
+ case "standard":
2593
+ return /* @__PURE__ */ new Set(["type", "status", "value", "deps", "meta", "v"]);
2594
+ case "full":
2595
+ return null;
2596
+ // null = include everything
2597
+ case "minimal":
2598
+ default:
2599
+ return /* @__PURE__ */ new Set(["type", "deps"]);
2600
+ }
2601
+ }
2530
2602
  function inferDescribeType(n) {
2531
2603
  if (n._describeKind != null) return n._describeKind;
2532
2604
  if (!n._hasDeps) return n._fn != null ? "producer" : "state";
@@ -2544,12 +2616,10 @@ function metaSnapshot(node2) {
2544
2616
  }
2545
2617
  return out;
2546
2618
  }
2547
- function describeNode(node2) {
2548
- const meta = { ...metaSnapshot(node2) };
2549
- const guard = node2 instanceof NodeImpl && node2._guard || node2 instanceof DynamicNodeImpl && node2._guard || void 0;
2550
- if (guard != null && meta.access === void 0) {
2551
- meta.access = accessHintForGuard(guard);
2552
- }
2619
+ function describeNode(node2, includeFields) {
2620
+ const all = includeFields == null;
2621
+ const metaKeys = !all && includeFields != null ? [...includeFields].filter((f) => f.startsWith("meta.")).map((f) => f.slice(5)) : null;
2622
+ const wantsMeta = all || includeFields.has("meta") || metaKeys != null && metaKeys.length > 0;
2553
2623
  let type = "state";
2554
2624
  let deps = [];
2555
2625
  if (node2 instanceof NodeImpl) {
@@ -2559,20 +2629,36 @@ function describeNode(node2) {
2559
2629
  type = node2._describeKind ?? "derived";
2560
2630
  deps = [];
2561
2631
  }
2562
- const out = {
2563
- type,
2564
- status: node2.status,
2565
- deps,
2566
- meta
2567
- };
2632
+ const out = { type, deps };
2633
+ if (all || includeFields.has("status")) {
2634
+ out.status = node2.status;
2635
+ }
2636
+ const guard = node2 instanceof NodeImpl && node2._guard || node2 instanceof DynamicNodeImpl && node2._guard || void 0;
2637
+ if (wantsMeta) {
2638
+ const rawMeta = { ...metaSnapshot(node2) };
2639
+ if (guard != null && rawMeta.access === void 0) {
2640
+ rawMeta.access = accessHintForGuard(guard);
2641
+ }
2642
+ if (metaKeys != null && metaKeys.length > 0 && !includeFields.has("meta")) {
2643
+ const filtered = {};
2644
+ for (const k of metaKeys) {
2645
+ if (k in rawMeta) filtered[k] = rawMeta[k];
2646
+ }
2647
+ out.meta = filtered;
2648
+ } else {
2649
+ out.meta = rawMeta;
2650
+ }
2651
+ }
2568
2652
  if (node2.name != null) {
2569
2653
  out.name = node2.name;
2570
2654
  }
2571
- try {
2572
- out.value = node2.get();
2573
- } catch {
2655
+ if (all || includeFields.has("value")) {
2656
+ try {
2657
+ out.value = node2.get();
2658
+ } catch {
2659
+ }
2574
2660
  }
2575
- if (node2.v != null) {
2661
+ if ((all || includeFields.has("v")) && node2.v != null) {
2576
2662
  const vInfo = { id: node2.v.id, version: node2.v.version };
2577
2663
  if ("cid" in node2.v) {
2578
2664
  vInfo.cid = node2.v.cid;
@@ -2580,6 +2666,16 @@ function describeNode(node2) {
2580
2666
  }
2581
2667
  out.v = vInfo;
2582
2668
  }
2669
+ if (all || includeFields.has("guard")) {
2670
+ if (guard != null) {
2671
+ out.guard = accessHintForGuard(guard);
2672
+ }
2673
+ }
2674
+ if (all || includeFields.has("lastMutation")) {
2675
+ if (node2.lastMutation != null) {
2676
+ out.lastMutation = node2.lastMutation;
2677
+ }
2678
+ }
2583
2679
  return out;
2584
2680
  }
2585
2681
 
@@ -2769,6 +2865,23 @@ function resolveSpyTheme(theme) {
2769
2865
  reset: theme.reset ?? ""
2770
2866
  };
2771
2867
  }
2868
+ function resolveObserveDetail(opts) {
2869
+ if (opts == null) return {};
2870
+ const detail = opts.detail;
2871
+ if (detail === "full") {
2872
+ return {
2873
+ ...opts,
2874
+ structured: opts.structured ?? true,
2875
+ timeline: opts.timeline ?? true,
2876
+ causal: opts.causal ?? true,
2877
+ derived: opts.derived ?? true
2878
+ };
2879
+ }
2880
+ if (detail === "minimal") {
2881
+ return { ...opts, structured: opts.structured ?? true };
2882
+ }
2883
+ return opts;
2884
+ }
2772
2885
  function assertLocalName(name, graphName, label) {
2773
2886
  if (name === "") {
2774
2887
  throw new Error(`Graph "${graphName}": ${label} name must be non-empty`);
@@ -2966,6 +3079,22 @@ var Graph = class _Graph {
2966
3079
  if (this._defaultVersioningLevel != null) {
2967
3080
  node2._applyVersioning(this._defaultVersioningLevel);
2968
3081
  }
3082
+ if (node2._deps.length > 0) {
3083
+ for (const dep of node2._deps) {
3084
+ for (const [depName, depNode] of this._nodes) {
3085
+ if (depNode === dep) {
3086
+ this._edges.add(edgeKey(depName, name));
3087
+ break;
3088
+ }
3089
+ }
3090
+ }
3091
+ }
3092
+ for (const [otherName, otherNode] of this._nodes) {
3093
+ if (otherName === name) continue;
3094
+ if (otherNode instanceof NodeImpl && otherNode._deps.includes(node2)) {
3095
+ this._edges.add(edgeKey(name, otherName));
3096
+ }
3097
+ }
2969
3098
  }
2970
3099
  }
2971
3100
  /**
@@ -3352,6 +3481,9 @@ var Graph = class _Graph {
3352
3481
  describe(options) {
3353
3482
  const actor = options?.actor;
3354
3483
  const filter = options?.filter;
3484
+ const includeFields = resolveDescribeFields(options?.detail, options?.fields);
3485
+ const isSpec = options?.format === "spec";
3486
+ const effectiveFields = isSpec ? resolveDescribeFields("minimal") : includeFields;
3355
3487
  const targets = [];
3356
3488
  this._collectObserveTargets("", targets);
3357
3489
  const nodeToPath = /* @__PURE__ */ new Map();
@@ -3361,7 +3493,7 @@ var Graph = class _Graph {
3361
3493
  const nodes = {};
3362
3494
  for (const [p, n] of targets) {
3363
3495
  if (actor != null && !n.allowsObserve(actor)) continue;
3364
- const raw = describeNode(n);
3496
+ const raw = describeNode(n, effectiveFields);
3365
3497
  const deps = n instanceof NodeImpl ? n._deps.map((d) => nodeToPath.get(d) ?? d.name ?? "") : [];
3366
3498
  const { name: _name, ...rest } = raw;
3367
3499
  const entry = { ...rest, deps };
@@ -3382,7 +3514,7 @@ var Graph = class _Graph {
3382
3514
  continue;
3383
3515
  }
3384
3516
  if (normalizedKey === "metaHas") {
3385
- if (!Object.hasOwn(entry.meta, String(fv))) {
3517
+ if (!Object.hasOwn(entry.meta ?? {}, String(fv))) {
3386
3518
  match = false;
3387
3519
  break;
3388
3520
  }
@@ -3415,11 +3547,24 @@ var Graph = class _Graph {
3415
3547
  const prefix = `${sg}${PATH_SEP}`;
3416
3548
  return [...nodeKeys].some((k) => k === sg || k.startsWith(prefix));
3417
3549
  }) : allSubgraphs;
3550
+ const graph = this;
3551
+ const baseOpts = options;
3418
3552
  return {
3419
3553
  name: this.name,
3420
3554
  nodes,
3421
3555
  edges,
3422
- subgraphs
3556
+ subgraphs,
3557
+ expand(detailOrFields) {
3558
+ const merged = { ...baseOpts, format: void 0 };
3559
+ if (Array.isArray(detailOrFields)) {
3560
+ merged.fields = detailOrFields;
3561
+ merged.detail = void 0;
3562
+ } else {
3563
+ merged.detail = detailOrFields;
3564
+ merged.fields = void 0;
3565
+ }
3566
+ return graph.describe(merged);
3567
+ }
3423
3568
  };
3424
3569
  }
3425
3570
  _collectSubgraphs(prefix) {
@@ -3472,14 +3617,15 @@ var Graph = class _Graph {
3472
3617
  observe(pathOrOpts, options) {
3473
3618
  if (typeof pathOrOpts === "string") {
3474
3619
  const path = pathOrOpts;
3475
- const actor2 = options?.actor;
3620
+ const resolved = resolveObserveDetail(options);
3621
+ const actor2 = resolved.actor;
3476
3622
  const target = this.resolve(path);
3477
3623
  if (actor2 != null && !target.allowsObserve(actor2)) {
3478
3624
  throw new GuardDenied({ actor: actor2, action: "observe", nodeName: path });
3479
3625
  }
3480
- const wantsStructured2 = options?.structured === true || options?.timeline === true || options?.causal === true || options?.derived === true;
3626
+ const wantsStructured2 = resolved.structured === true || resolved.timeline === true || resolved.causal === true || resolved.derived === true || resolved.detail === "minimal" || resolved.detail === "full";
3481
3627
  if (wantsStructured2 && _Graph.inspectorEnabled) {
3482
- return this._createObserveResult(path, target, options);
3628
+ return this._createObserveResult(path, target, resolved);
3483
3629
  }
3484
3630
  return {
3485
3631
  subscribe(sink) {
@@ -3495,11 +3641,11 @@ var Graph = class _Graph {
3495
3641
  }
3496
3642
  };
3497
3643
  }
3498
- const opts = pathOrOpts;
3499
- const actor = opts?.actor;
3500
- const wantsStructured = opts?.structured === true || opts?.timeline === true || opts?.causal === true || opts?.derived === true;
3644
+ const opts = resolveObserveDetail(pathOrOpts);
3645
+ const actor = opts.actor;
3646
+ const wantsStructured = opts.structured === true || opts.timeline === true || opts.causal === true || opts.derived === true || opts.detail === "minimal" || opts.detail === "full";
3501
3647
  if (wantsStructured && _Graph.inspectorEnabled) {
3502
- return this._createObserveResultForAll(opts ?? {});
3648
+ return this._createObserveResultForAll(opts);
3503
3649
  }
3504
3650
  return {
3505
3651
  subscribe: (sink) => {
@@ -3531,6 +3677,7 @@ var Graph = class _Graph {
3531
3677
  const timeline = options.timeline === true;
3532
3678
  const causal = options.causal === true;
3533
3679
  const derived2 = options.derived === true;
3680
+ const minimal = options.detail === "minimal";
3534
3681
  const result = {
3535
3682
  values: {},
3536
3683
  dirtyCount: 0,
@@ -3576,6 +3723,11 @@ var Graph = class _Graph {
3576
3723
  if (t === DATA) {
3577
3724
  result.values[path] = m[1];
3578
3725
  result.events.push({ type: "data", path, data: m[1], ...base, ...withCausal });
3726
+ } else if (minimal) {
3727
+ if (t === DIRTY) result.dirtyCount++;
3728
+ else if (t === RESOLVED) result.resolvedCount++;
3729
+ else if (t === COMPLETE && !result.errored) result.completedCleanly = true;
3730
+ else if (t === ERROR) result.errored = true;
3579
3731
  } else if (t === DIRTY) {
3580
3732
  result.dirtyCount++;
3581
3733
  result.events.push({ type: "dirty", path, ...base });
@@ -3591,6 +3743,8 @@ var Graph = class _Graph {
3591
3743
  }
3592
3744
  }
3593
3745
  });
3746
+ const graph = this;
3747
+ const basePath = path;
3594
3748
  return {
3595
3749
  get values() {
3596
3750
  return result.values;
@@ -3613,11 +3767,28 @@ var Graph = class _Graph {
3613
3767
  dispose() {
3614
3768
  unsub();
3615
3769
  detachInspectorHook?.();
3770
+ },
3771
+ expand(extra) {
3772
+ unsub();
3773
+ detachInspectorHook?.();
3774
+ const merged = { ...options };
3775
+ if (typeof extra === "string") {
3776
+ merged.detail = extra;
3777
+ } else {
3778
+ Object.assign(merged, extra);
3779
+ }
3780
+ const resolvedTarget = graph.resolve(basePath);
3781
+ return graph._createObserveResult(
3782
+ basePath,
3783
+ resolvedTarget,
3784
+ resolveObserveDetail(merged)
3785
+ );
3616
3786
  }
3617
3787
  };
3618
3788
  }
3619
3789
  _createObserveResultForAll(options) {
3620
3790
  const timeline = options.timeline === true;
3791
+ const minimal = options.detail === "minimal";
3621
3792
  const result = {
3622
3793
  values: {},
3623
3794
  dirtyCount: 0,
@@ -3639,6 +3810,11 @@ var Graph = class _Graph {
3639
3810
  if (t === DATA) {
3640
3811
  result.values[path] = m[1];
3641
3812
  result.events.push({ type: "data", path, data: m[1], ...base });
3813
+ } else if (minimal) {
3814
+ if (t === DIRTY) result.dirtyCount++;
3815
+ else if (t === RESOLVED) result.resolvedCount++;
3816
+ else if (t === COMPLETE && !result.errored) result.completedCleanly = true;
3817
+ else if (t === ERROR) result.errored = true;
3642
3818
  } else if (t === DIRTY) {
3643
3819
  result.dirtyCount++;
3644
3820
  result.events.push({ type: "dirty", path, ...base });
@@ -3655,6 +3831,7 @@ var Graph = class _Graph {
3655
3831
  }
3656
3832
  })
3657
3833
  );
3834
+ const graph = this;
3658
3835
  return {
3659
3836
  get values() {
3660
3837
  return result.values;
@@ -3676,6 +3853,16 @@ var Graph = class _Graph {
3676
3853
  },
3677
3854
  dispose() {
3678
3855
  for (const u of unsubs) u();
3856
+ },
3857
+ expand(extra) {
3858
+ for (const u of unsubs) u();
3859
+ const merged = { ...options };
3860
+ if (typeof extra === "string") {
3861
+ merged.detail = extra;
3862
+ } else {
3863
+ Object.assign(merged, extra);
3864
+ }
3865
+ return graph._createObserveResultForAll(resolveObserveDetail(merged));
3679
3866
  }
3680
3867
  };
3681
3868
  }
@@ -3751,6 +3938,9 @@ var Graph = class _Graph {
3751
3938
  },
3752
3939
  dispose() {
3753
3940
  stop2();
3941
+ },
3942
+ expand() {
3943
+ throw new Error("expand() requires inspector mode (Graph.inspectorEnabled = true)");
3754
3944
  }
3755
3945
  };
3756
3946
  const pushEvent = (path, message) => {
@@ -3851,16 +4041,16 @@ var Graph = class _Graph {
3851
4041
  * @returns Rendered graph text.
3852
4042
  */
3853
4043
  dumpGraph(options = {}) {
3854
- const described = this.describe({
4044
+ const { expand: _, ...described } = this.describe({
3855
4045
  actor: options.actor,
3856
- filter: options.filter
4046
+ filter: options.filter,
4047
+ detail: "standard"
3857
4048
  });
3858
4049
  const includeEdges = options.includeEdges ?? true;
3859
4050
  const includeSubgraphs = options.includeSubgraphs ?? true;
3860
4051
  if (options.format === "json") {
3861
4052
  const payload = {
3862
- name: described.name,
3863
- nodes: described.nodes,
4053
+ ...described,
3864
4054
  edges: includeEdges ? described.edges : [],
3865
4055
  subgraphs: includeSubgraphs ? described.subgraphs : []
3866
4056
  };
@@ -3930,10 +4120,11 @@ var Graph = class _Graph {
3930
4120
  * @returns Persistable snapshot with sorted keys.
3931
4121
  */
3932
4122
  snapshot() {
3933
- const d = this.describe();
4123
+ const { expand: _, ...d } = this.describe({ detail: "full" });
3934
4124
  const sortedNodes = {};
3935
4125
  for (const key of Object.keys(d.nodes).sort()) {
3936
- sortedNodes[key] = d.nodes[key];
4126
+ const { lastMutation: _lm, guard: _g, ...node2 } = d.nodes[key];
4127
+ sortedNodes[key] = node2;
3937
4128
  }
3938
4129
  const sortedSubgraphs = [...d.subgraphs].sort();
3939
4130
  return { ...d, version: 1, nodes: sortedNodes, subgraphs: sortedSubgraphs };
@@ -4049,20 +4240,28 @@ var Graph = class _Graph {
4049
4240
  return g;
4050
4241
  }
4051
4242
  /**
4052
- * Plain snapshot with **recursively sorted object keys** for deterministic serialization (§3.8).
4243
+ * Plain snapshot object with **recursively sorted object keys** for deterministic serialization (§3.8).
4053
4244
  *
4054
4245
  * @remarks
4055
- * ECMAScript `JSON.stringify(graph)` invokes this method; it must return a plain object, not an
4056
- * already-stringified JSON string (otherwise the graph would be double-encoded).
4057
4246
  * For a single UTF-8 string with a trailing newline (convenient for git), use {@link Graph.toJSONString}.
4058
4247
  *
4059
4248
  * @returns Same object as {@link Graph.snapshot}.
4060
4249
  */
4061
- toJSON() {
4250
+ toObject() {
4062
4251
  return this.snapshot();
4063
4252
  }
4064
4253
  /**
4065
- * Deterministic JSON **text**: `JSON.stringify` of {@link Graph.toJSON} plus a trailing newline (§3.8).
4254
+ * ECMAScript `JSON.stringify` hook — delegates to {@link Graph.toObject}.
4255
+ *
4256
+ * @remarks
4257
+ * Must return a plain object (not a string) so `JSON.stringify(graph)` works correctly
4258
+ * without double-encoding.
4259
+ */
4260
+ toJSON() {
4261
+ return this.toObject();
4262
+ }
4263
+ /**
4264
+ * Deterministic JSON **text**: `JSON.stringify` of {@link Graph.toObject} plus a trailing newline (§3.8).
4066
4265
  *
4067
4266
  * @returns Stable string suitable for diffs.
4068
4267
  */
@@ -4087,16 +4286,22 @@ var Graph = class _Graph {
4087
4286
  if (!pending) return;
4088
4287
  pending = false;
4089
4288
  try {
4090
- const described = this.describe();
4289
+ const { expand: _expand, ...raw } = this.describe({ detail: "full" });
4290
+ const cleanNodes = {};
4291
+ for (const [p, n] of Object.entries(raw.nodes)) {
4292
+ const { lastMutation: _lm, guard: _g, ...node2 } = n;
4293
+ cleanNodes[p] = node2;
4294
+ }
4295
+ const described = { ...raw, nodes: cleanNodes };
4091
4296
  const snapshot = { ...described, version: SNAPSHOT_VERSION };
4092
4297
  seq += 1;
4093
4298
  const shouldCompact = lastDescribe == null || seq % compactEvery === 0;
4094
4299
  if (shouldCompact) {
4095
- adapter.save({ mode: "full", snapshot, seq });
4300
+ adapter.save(this.name, { mode: "full", snapshot, seq });
4096
4301
  } else {
4097
4302
  const previous = lastDescribe;
4098
4303
  if (previous == null) return;
4099
- adapter.save({
4304
+ adapter.save(this.name, {
4100
4305
  mode: "diff",
4101
4306
  diff: _Graph.diff(previous, described),
4102
4307
  snapshot,
@@ -4117,8 +4322,10 @@ var Graph = class _Graph {
4117
4322
  const triggeredByTier = messages.some((m) => messageTier(m[0]) >= 2);
4118
4323
  if (!triggeredByTier) return;
4119
4324
  if (options.filter) {
4120
- const described = this.describe().nodes[path];
4121
- if (described == null || !options.filter(path, described)) return;
4325
+ const nd = this.resolve(path);
4326
+ if (nd == null) return;
4327
+ const described = describeNode(nd, resolveDescribeFields("standard"));
4328
+ if (!options.filter(path, described)) return;
4122
4329
  }
4123
4330
  schedule();
4124
4331
  });