@contember/bindx 0.1.46 → 0.1.47

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 (118) hide show
  1. package/dist/core/ActionDispatcher.d.ts +4 -1
  2. package/dist/core/ActionDispatcher.d.ts.map +1 -1
  3. package/dist/core/actions.d.ts +7 -1
  4. package/dist/core/actions.d.ts.map +1 -1
  5. package/dist/events/EventEmitter.d.ts +13 -1
  6. package/dist/events/EventEmitter.d.ts.map +1 -1
  7. package/dist/handles/BaseHandle.d.ts +14 -3
  8. package/dist/handles/BaseHandle.d.ts.map +1 -1
  9. package/dist/handles/EntityHandle.d.ts +14 -5
  10. package/dist/handles/EntityHandle.d.ts.map +1 -1
  11. package/dist/handles/FieldHandle.d.ts +11 -4
  12. package/dist/handles/FieldHandle.d.ts.map +1 -1
  13. package/dist/handles/HasManyListHandle.d.ts +36 -2
  14. package/dist/handles/HasManyListHandle.d.ts.map +1 -1
  15. package/dist/handles/HasOneHandle.d.ts +52 -9
  16. package/dist/handles/HasOneHandle.d.ts.map +1 -1
  17. package/dist/handles/PlaceholderHandle.d.ts +25 -13
  18. package/dist/handles/PlaceholderHandle.d.ts.map +1 -1
  19. package/dist/handles/index.d.ts +1 -1
  20. package/dist/handles/index.d.ts.map +1 -1
  21. package/dist/handles/proxyFactory.d.ts +7 -1
  22. package/dist/handles/proxyFactory.d.ts.map +1 -1
  23. package/dist/handles/types.d.ts +33 -0
  24. package/dist/handles/types.d.ts.map +1 -1
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/persistence/BatchPersister.d.ts +53 -37
  28. package/dist/persistence/BatchPersister.d.ts.map +1 -1
  29. package/dist/persistence/ChangeRegistry.d.ts +15 -0
  30. package/dist/persistence/ChangeRegistry.d.ts.map +1 -1
  31. package/dist/persistence/MutationCollector.d.ts +106 -3
  32. package/dist/persistence/MutationCollector.d.ts.map +1 -1
  33. package/dist/persistence/PersistExecution.d.ts +26 -0
  34. package/dist/persistence/PersistExecution.d.ts.map +1 -0
  35. package/dist/persistence/types.d.ts +8 -2
  36. package/dist/persistence/types.d.ts.map +1 -1
  37. package/dist/store/EntityMetaStore.d.ts +32 -10
  38. package/dist/store/EntityMetaStore.d.ts.map +1 -1
  39. package/dist/store/EntitySnapshotStore.d.ts +50 -3
  40. package/dist/store/EntitySnapshotStore.d.ts.map +1 -1
  41. package/dist/store/ErrorStore.d.ts +3 -2
  42. package/dist/store/ErrorStore.d.ts.map +1 -1
  43. package/dist/store/HasManyStore.d.ts +214 -0
  44. package/dist/store/HasManyStore.d.ts.map +1 -0
  45. package/dist/store/HasOneStore.d.ts +159 -0
  46. package/dist/store/HasOneStore.d.ts.map +1 -0
  47. package/dist/store/ReachabilityAnalyzer.d.ts +19 -0
  48. package/dist/store/ReachabilityAnalyzer.d.ts.map +1 -1
  49. package/dist/store/RekeyOrchestrator.d.ts +76 -0
  50. package/dist/store/RekeyOrchestrator.d.ts.map +1 -0
  51. package/dist/store/RelationEdgeIndex.d.ts +34 -0
  52. package/dist/store/RelationEdgeIndex.d.ts.map +1 -0
  53. package/dist/store/RelationStore.d.ts +74 -137
  54. package/dist/store/RelationStore.d.ts.map +1 -1
  55. package/dist/store/RootRegistry.d.ts +25 -4
  56. package/dist/store/RootRegistry.d.ts.map +1 -1
  57. package/dist/store/SnapshotStore.d.ts +137 -13
  58. package/dist/store/SnapshotStore.d.ts.map +1 -1
  59. package/dist/store/SubscriptionManager.d.ts +63 -14
  60. package/dist/store/SubscriptionManager.d.ts.map +1 -1
  61. package/dist/store/TouchedStore.d.ts +3 -2
  62. package/dist/store/TouchedStore.d.ts.map +1 -1
  63. package/dist/store/relationKey.d.ts +15 -0
  64. package/dist/store/relationKey.d.ts.map +1 -0
  65. package/dist/undo/UndoJournal.d.ts +133 -0
  66. package/dist/undo/UndoJournal.d.ts.map +1 -0
  67. package/dist/undo/UndoManager.d.ts +25 -82
  68. package/dist/undo/UndoManager.d.ts.map +1 -1
  69. package/dist/undo/UnrecordedWriteError.d.ts +16 -0
  70. package/dist/undo/UnrecordedWriteError.d.ts.map +1 -0
  71. package/dist/undo/index.d.ts +3 -1
  72. package/dist/undo/index.d.ts.map +1 -1
  73. package/dist/undo/rekeyJournalEntry.d.ts +20 -0
  74. package/dist/undo/rekeyJournalEntry.d.ts.map +1 -0
  75. package/dist/undo/types.d.ts +0 -48
  76. package/dist/undo/types.d.ts.map +1 -1
  77. package/package.json +2 -2
  78. package/src/core/ActionDispatcher.ts +56 -36
  79. package/src/core/actions.ts +8 -1
  80. package/src/events/EventEmitter.ts +78 -24
  81. package/src/handles/BaseHandle.ts +19 -2
  82. package/src/handles/EntityHandle.ts +48 -17
  83. package/src/handles/FieldHandle.ts +36 -10
  84. package/src/handles/HasManyListHandle.ts +120 -16
  85. package/src/handles/HasOneHandle.ts +177 -44
  86. package/src/handles/PlaceholderHandle.ts +184 -36
  87. package/src/handles/index.ts +3 -0
  88. package/src/handles/proxyFactory.ts +27 -0
  89. package/src/handles/types.ts +39 -0
  90. package/src/index.ts +4 -4
  91. package/src/persistence/BatchPersister.ts +810 -446
  92. package/src/persistence/ChangeRegistry.ts +27 -3
  93. package/src/persistence/MutationCollector.ts +265 -31
  94. package/src/persistence/PersistExecution.ts +217 -0
  95. package/src/persistence/types.ts +8 -2
  96. package/src/store/EntityMetaStore.ts +89 -45
  97. package/src/store/EntitySnapshotStore.ts +102 -19
  98. package/src/store/ErrorStore.ts +6 -5
  99. package/src/store/HasManyStore.ts +810 -0
  100. package/src/store/HasOneStore.ts +454 -0
  101. package/src/store/ReachabilityAnalyzer.ts +37 -0
  102. package/src/store/RekeyOrchestrator.ts +123 -0
  103. package/src/store/RelationEdgeIndex.ts +79 -0
  104. package/src/store/RelationStore.ts +176 -725
  105. package/src/store/RootRegistry.ts +50 -8
  106. package/src/store/SnapshotStore.ts +572 -101
  107. package/src/store/SubscriptionManager.ts +122 -75
  108. package/src/store/TouchedStore.ts +5 -2
  109. package/src/store/relationKey.ts +20 -0
  110. package/src/undo/UndoJournal.ts +258 -0
  111. package/src/undo/UndoManager.ts +151 -281
  112. package/src/undo/UnrecordedWriteError.ts +23 -0
  113. package/src/undo/index.ts +8 -7
  114. package/src/undo/rekeyJournalEntry.ts +145 -0
  115. package/src/undo/types.ts +0 -58
  116. package/dist/core/actionClassification.d.ts +0 -48
  117. package/dist/core/actionClassification.d.ts.map +0 -1
  118. package/src/core/actionClassification.ts +0 -199
@@ -3,117 +3,60 @@ import type { SnapshotStore } from '../store/SnapshotStore.js';
3
3
  import type { UndoManagerConfig, UndoState } from './types.js';
4
4
  type Subscriber = () => void;
5
5
  /**
6
- * UndoManager provides undo/redo functionality for the Bindx store.
6
+ * UndoManager — the policy layer over the {@link UndoJournal}.
7
7
  *
8
- * Features:
9
- * - Automatic debounced grouping of rapid changes
10
- * - Manual grouping via beginGroup/endGroup
11
- * - Configurable history size
12
- * - Block/unblock during persist operations
13
- * - React integration via subscribe/getState
8
+ * The journal records each gesture (one dispatch / one handle transaction) as a
9
+ * {@link JournalEntry} of editable-layer pre-images. UndoManager owns the undo /
10
+ * redo stacks and the grouping policy (debounced auto-grouping + manual
11
+ * begin/endGroup), blocks during persist, and keeps the stacks aligned across a
12
+ * temp→persisted rekey. Restore goes straight through the store's write paths, so
13
+ * the edge index and reachability cache rebuild themselves; the event/interceptor
14
+ * pipeline is NOT replayed.
14
15
  */
15
16
  export declare class UndoManager {
16
17
  private readonly store;
18
+ private readonly journal;
17
19
  private undoStack;
18
20
  private redoStack;
19
- private pendingEntry;
21
+ /** Cells accumulated while a debounce window or manual group is open (first-writer-wins). */
22
+ private pending;
20
23
  private debounceTimer;
21
- private isBlocked;
22
24
  private manualGroupId;
23
- private manualGroupLabel;
25
+ private blockDepth;
24
26
  private subscribers;
25
27
  private cachedState;
26
28
  private readonly maxHistorySize;
27
29
  private readonly debounceMs;
28
30
  constructor(store: SnapshotStore, config?: UndoManagerConfig);
29
31
  /**
30
- * Creates middleware for ActionDispatcher.
31
- * Must be added to dispatcher to enable undo tracking.
32
+ * Wires the journal into the store so its write paths record gestures. The
33
+ * returned middleware is a backward-compatible pass-through (recording is native
34
+ * to the store transaction, not the middleware).
32
35
  */
33
36
  createMiddleware(): ActionMiddleware;
34
- /**
35
- * Handles an action, capturing state for undo if trackable.
36
- */
37
- private handleAction;
38
- /**
39
- * Handles action in manual group mode.
40
- */
41
- private handleManualGroupAction;
42
- /**
43
- * Handles action in debounced auto-group mode.
44
- */
45
- private handleDebouncedAction;
46
- /**
47
- * Gets keys that are in source but not in target.
48
- */
49
- private getNewKeys;
50
- /**
51
- * Checks if keys object is empty.
52
- */
53
- private isEmptyKeys;
54
- /**
55
- * Merges additional snapshot into target.
56
- */
57
- private mergeSnapshots;
58
- /**
59
- * Starts the debounce timer.
60
- */
61
- private startDebounceTimer;
62
- /**
63
- * Resets the debounce timer.
64
- */
37
+ private onEntry;
38
+ private mergeIntoPending;
39
+ private flushPending;
40
+ private pushEntry;
65
41
  private resetDebounceTimer;
66
42
  /**
67
- * Flushes pending entry to undo stack.
68
- */
69
- private flushPendingEntry;
70
- /**
71
- * Trims history to max size.
72
- */
73
- private trimHistory;
74
- /**
75
- * Starts a manual group. All actions until endGroup are grouped as one undo entry.
76
- * Returns group ID that must be passed to endGroup.
77
- */
78
- beginGroup(label?: string): string;
79
- /**
80
- * Ends a manual group and pushes to undo stack.
43
+ * Starts a manual group: every gesture until {@link endGroup} folds into one
44
+ * undo entry. Returns a handle that must be passed back to endGroup.
81
45
  */
46
+ beginGroup(_label?: string): string;
82
47
  endGroup(groupId: string): void;
83
- /**
84
- * Undoes the last entry.
85
- */
86
48
  undo(): void;
87
- /**
88
- * Redoes the last undone entry.
89
- */
90
49
  redo(): void;
91
- /**
92
- * Blocks undo/redo operations.
93
- * Use during persist to prevent inconsistent state.
94
- */
95
50
  block(): void;
96
- /**
97
- * Unblocks undo/redo operations.
98
- */
99
51
  unblock(): void;
100
52
  /**
101
- * Clears all undo/redo history.
53
+ * Rewrites every stacked entry when a temp id is replaced by its persisted id,
54
+ * so stored cells keep valid keys / id references (and sealed creates drop out).
102
55
  */
56
+ private rekeyStacks;
103
57
  clear(): void;
104
- /**
105
- * Subscribes to state changes.
106
- * Returns unsubscribe function.
107
- */
108
58
  subscribe(callback: Subscriber): () => void;
109
- /**
110
- * Gets current state for React integration.
111
- * Returns a cached object to satisfy useSyncExternalStore's referential equality requirement.
112
- */
113
59
  getState(): UndoState;
114
- /**
115
- * Notifies all subscribers of state change.
116
- */
117
60
  private notifySubscribers;
118
61
  }
119
62
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"UndoManager.d.ts","sourceRoot":"","sources":["../../src/undo/UndoManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AASnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EAIX,iBAAiB,EACjB,SAAS,EACT,MAAM,YAAY,CAAA;AAEnB,KAAK,UAAU,GAAG,MAAM,IAAI,CAAA;AAS5B;;;;;;;;;GASG;AACH,qBAAa,WAAW;IAetB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAdvB,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,WAAW,CAAyB;IAE5C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;gBAGjB,KAAK,EAAE,aAAa,EACrC,MAAM,GAAE,iBAAsB;IAM/B;;;OAGG;IACH,gBAAgB,IAAI,gBAAgB;IAQpC;;OAEG;IACH,OAAO,CAAC,YAAY;IAuBpB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAsB/B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAqC7B;;OAEG;IACH,OAAO,CAAC,UAAU;IAQlB;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACH,OAAO,CAAC,cAAc;IAuBtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAqBzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAMnB;;;OAGG;IACH,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAUlC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAW/B;;OAEG;IACH,IAAI,IAAI,IAAI;IA4BZ;;OAEG;IACH,IAAI,IAAI,IAAI;IAyBZ;;;OAGG;IACH,KAAK,IAAI,IAAI;IAOb;;OAEG;IACH,OAAO,IAAI,IAAI;IAKf;;OAEG;IACH,KAAK,IAAI,IAAI;IAab;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,IAAI;IAO3C;;;OAGG;IACH,QAAQ,IAAI,SAAS;IAarB;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAMzB"}
1
+ {"version":3,"file":"UndoManager.d.ts","sourceRoot":"","sources":["../../src/undo/UndoManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAI9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE9D,KAAK,UAAU,GAAG,MAAM,IAAI,CAAA;AAS5B;;;;;;;;;;GAUG;AACH,qBAAa,WAAW;IAkBtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAjBvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;IACrC,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,SAAS,CAAqB;IAEtC,6FAA6F;IAC7F,OAAO,CAAC,OAAO,CAA6C;IAC5D,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,aAAa,CAAsB;IAE3C,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,WAAW,CAAyB;IAE5C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;gBAGjB,KAAK,EAAE,aAAa,EACrC,MAAM,GAAE,iBAAsB;IAY/B;;;;OAIG;IACH,gBAAgB,IAAI,gBAAgB;IAWpC,OAAO,CAAC,OAAO;IAwBf,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,kBAAkB;IAS1B;;;OAGG;IACH,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;IAOnC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAW/B,IAAI,IAAI,IAAI;IAcZ,IAAI,IAAI,IAAI;IAaZ,KAAK,IAAI,IAAI;IAUb,OAAO,IAAI,IAAI;IAUf;;;OAGG;IACH,OAAO,CAAC,WAAW;IAiCnB,KAAK,IAAI,IAAI;IAYb,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,IAAI;IAO3C,QAAQ,IAAI,SAAS;IAcrB,OAAO,CAAC,iBAAiB;CAMzB"}
@@ -0,0 +1,16 @@
1
+ import type { JournalCellKind } from './UndoJournal.js';
2
+ /**
3
+ * Thrown when a journal transaction closes after an editable-layer store write
4
+ * that was NOT preceded by a matching `journal.record*` call — i.e. a mutating
5
+ * SnapshotStore method wrote primary state a gesture can no longer undo.
6
+ *
7
+ * This turns the "every mutating method must record before writing" convention
8
+ * into a checked invariant: the sub-store editable-write counters advanced for a
9
+ * kind the transaction never recorded. The named kinds pinpoint which record call
10
+ * is missing (recordEntity / recordRelation / recordHasMany).
11
+ */
12
+ export declare class UnrecordedWriteError extends Error {
13
+ readonly kinds: ReadonlyArray<JournalCellKind>;
14
+ constructor(kinds: ReadonlyArray<JournalCellKind>);
15
+ }
16
+ //# sourceMappingURL=UnrecordedWriteError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UnrecordedWriteError.d.ts","sourceRoot":"","sources":["../../src/undo/UnrecordedWriteError.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEvD;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aAClB,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC;gBAArC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC;CASjE"}
@@ -1,3 +1,5 @@
1
1
  export { UndoManager } from './UndoManager.js';
2
- export type { PartialStoreSnapshot, PendingUndoEntry, UndoEntry, UndoManagerConfig, UndoState, StoreAffectedKeys, } from './types.js';
2
+ export { UnrecordedWriteError } from './UnrecordedWriteError.js';
3
+ export type { UndoManagerConfig, UndoState } from './types.js';
4
+ export type { JournalEntry, JournalCellImage, EntityCellImage, RelationCellImage, HasManyCellImage, } from './UndoJournal.js';
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/undo/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,YAAY,EACX,oBAAoB,EACpB,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,SAAS,EACT,iBAAiB,GACjB,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/undo/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC9D,YAAY,EACX,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GAChB,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,20 @@
1
+ import type { RekeyContext } from '../store/RekeyOrchestrator.js';
2
+ import type { JournalEntry } from './UndoJournal.js';
3
+ /**
4
+ * Rewrites a journal entry for a temp→persisted rekey so stored cells stay valid
5
+ * after persist:
6
+ * - owner keys move from the temp prefix to the persisted prefix;
7
+ * - embedded id references (snapshot ids, relation currentId/serverId, has-many
8
+ * members) are remapped oldId→newId;
9
+ * - the create is SEALED: an "absent" pre-image of the now-persisted entity (or
10
+ * its owned relations) is dropped, so undo can no longer delete the
11
+ * server-backed row — only later edits to it remain undoable.
12
+ */
13
+ /**
14
+ * Looks up the live server-member ids of a has-many list by its key. Lets the
15
+ * rekey rebase a pre-image when the just-persisted create has become a permanent
16
+ * member of that list.
17
+ */
18
+ export type LiveServerIdsLookup = (relationKey: string) => Set<string>;
19
+ export declare function rekeyJournalEntry(entry: JournalEntry, ctx: RekeyContext, liveServerIds: LiveServerIdsLookup): JournalEntry;
20
+ //# sourceMappingURL=rekeyJournalEntry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rekeyJournalEntry.d.ts","sourceRoot":"","sources":["../../src/undo/rekeyJournalEntry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAIjE,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,kBAAkB,CAAA;AAEtE;;;;;;;;;GASG;AACH;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,WAAW,EAAE,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,CAAA;AAEtE,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,YAAY,EACjB,aAAa,EAAE,mBAAmB,GAChC,YAAY,CAOd"}
@@ -1,50 +1,3 @@
1
- import type { StoreAffectedKeys } from '../core/actionClassification.js';
2
- import type { EntitySnapshot } from '../store/snapshots.js';
3
- import type { StoredRelationState, StoredHasManyState, EntityMeta } from '../store/SnapshotStore.js';
4
- /**
5
- * Partial snapshot of store state.
6
- * Contains only the data affected by an action or group of actions.
7
- */
8
- export interface PartialStoreSnapshot {
9
- /** Entity snapshots (frozen, immutable) */
10
- entitySnapshots: Map<string, EntitySnapshot>;
11
- /** Has-one relation states */
12
- relationStates: Map<string, StoredRelationState>;
13
- /** Has-many list states */
14
- hasManyStates: Map<string, StoredHasManyState>;
15
- /** Entity metadata (existsOnServer, deletion flags) */
16
- entityMetas: Map<string, EntityMeta>;
17
- }
18
- /**
19
- * A single undo/redo entry.
20
- * Contains the snapshot of state BEFORE the action(s) were applied.
21
- */
22
- export interface UndoEntry {
23
- /** Unique identifier for this entry */
24
- id: string;
25
- /** Optional human-readable label */
26
- label?: string;
27
- /** Snapshot of state BEFORE the action(s) */
28
- beforeSnapshot: PartialStoreSnapshot;
29
- /** Keys that were affected (for redo snapshot capture) */
30
- affectedKeys: StoreAffectedKeys;
31
- /** When this entry was created */
32
- timestamp: number;
33
- }
34
- /**
35
- * Pending entry during debounce window.
36
- * Accumulates affected keys while waiting for debounce to complete.
37
- */
38
- export interface PendingUndoEntry {
39
- /** Snapshot captured before the first action in the group */
40
- beforeSnapshot: PartialStoreSnapshot;
41
- /** Accumulated affected keys */
42
- affectedKeys: StoreAffectedKeys;
43
- /** When the first action occurred */
44
- timestamp: number;
45
- /** Optional label for manual grouping */
46
- label?: string;
47
- }
48
1
  /**
49
2
  * Configuration options for UndoManager.
50
3
  */
@@ -70,5 +23,4 @@ export interface UndoState {
70
23
  /** Number of entries in redo stack */
71
24
  redoCount: number;
72
25
  }
73
- export type { StoreAffectedKeys } from '../core/actionClassification.js';
74
26
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/undo/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EACX,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,EACV,MAAM,2BAA2B,CAAA;AAElC;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,2CAA2C;IAC3C,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IAC5C,8BAA8B;IAC9B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IAChD,2BAA2B;IAC3B,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IAC9C,uDAAuD;IACvD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB,uCAAuC;IACvC,EAAE,EAAE,MAAM,CAAA;IACV,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6CAA6C;IAC7C,cAAc,EAAE,oBAAoB,CAAA;IACpC,0DAA0D;IAC1D,YAAY,EAAE,iBAAiB,CAAA;IAC/B,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAA;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAChC,6DAA6D;IAC7D,cAAc,EAAE,oBAAoB,CAAA;IACpC,gCAAgC;IAChC,YAAY,EAAE,iBAAiB,CAAA;IAC/B,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB,gCAAgC;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,gCAAgC;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAA;IAClB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAA;CACjB;AAGD,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/undo/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB,gCAAgC;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,gCAAgC;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAA;IAClB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAA;CACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/bindx",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "description": "Type-safe data binding framework (core)",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
- "@contember/bindx-client": "0.1.46",
20
+ "@contember/bindx-client": "0.1.47",
21
21
  "@contember/graphql-client": "^2.1.0-beta.1"
22
22
  },
23
23
  "repository": {
@@ -27,6 +27,7 @@ export class ActionDispatcher {
27
27
  eventEmitter?: EventEmitter,
28
28
  ) {
29
29
  this.eventEmitter = eventEmitter ?? new EventEmitter()
30
+ this.store.attachRekeyParticipant(this.eventEmitter)
30
31
  }
31
32
 
32
33
  /**
@@ -42,39 +43,46 @@ export class ActionDispatcher {
42
43
  * Async interceptors are skipped with a warning — use dispatchAsync() for those.
43
44
  */
44
45
  dispatch(action: Action): void {
45
- // Run through middlewares first
46
- for (const middleware of this.middlewares) {
47
- const result = middleware(action, this.store)
48
- if (result === false) {
49
- // Middleware can cancel the action
50
- return
46
+ // One dispatch = one undoable gesture. The store records every primary write
47
+ // made between begin/commit into a single journal entry (no-op without undo).
48
+ this.store.beginTransaction()
49
+ try {
50
+ // Run through middlewares first
51
+ for (const middleware of this.middlewares) {
52
+ const result = middleware(action, this.store)
53
+ if (result === false) {
54
+ // Middleware can cancel the action
55
+ return
56
+ }
51
57
  }
52
- }
53
58
 
54
- // Create and run before event through interceptors (synchronously)
55
- const beforeEvent = createBeforeEvent(action, this.store)
56
- if (beforeEvent) {
57
- const result = this.eventEmitter.runInterceptorsSync(beforeEvent)
58
- if (result === null) {
59
- // Interceptor cancelled the action
60
- return
61
- }
62
- // If event was modified, update action accordingly
63
- if (result !== beforeEvent) {
64
- action = this.updateActionFromEvent(action, result)
59
+ // Create and run before event through interceptors (synchronously)
60
+ const beforeEvent = createBeforeEvent(action, this.store)
61
+ if (beforeEvent) {
62
+ const result = this.eventEmitter.runInterceptorsSync(beforeEvent)
63
+ if (result === null) {
64
+ // Interceptor cancelled the action
65
+ return
66
+ }
67
+ // If event was modified, update action accordingly
68
+ if (result !== beforeEvent) {
69
+ action = this.updateActionFromEvent(action, result)
70
+ }
65
71
  }
66
- }
67
72
 
68
- // Capture state before execution (for after events)
69
- const stateBefore = captureStateBeforeAction(action, this.store)
73
+ // Capture state before execution (for after events)
74
+ const stateBefore = captureStateBeforeAction(action, this.store)
70
75
 
71
- // Execute the action
72
- this.execute(action)
76
+ // Execute the action
77
+ this.execute(action)
73
78
 
74
- // Emit after event
75
- const afterEvent = createAfterEvent(action, stateBefore, this.store)
76
- if (afterEvent) {
77
- this.eventEmitter.emit(afterEvent)
79
+ // Emit after event
80
+ const afterEvent = createAfterEvent(action, stateBefore, this.store)
81
+ if (afterEvent) {
82
+ this.eventEmitter.emit(afterEvent)
83
+ }
84
+ } finally {
85
+ this.store.commitTransaction()
78
86
  }
79
87
  }
80
88
 
@@ -108,16 +116,21 @@ export class ActionDispatcher {
108
116
  // Capture state before execution (for after events)
109
117
  const stateBefore = captureStateBeforeAction(action, this.store)
110
118
 
111
- // Execute the action
112
- this.execute(action)
119
+ this.store.beginTransaction()
120
+ try {
121
+ // Execute the action
122
+ this.execute(action)
113
123
 
114
- // Emit after event
115
- const afterEvent = createAfterEvent(action, stateBefore, this.store)
116
- if (afterEvent) {
117
- this.eventEmitter.emit(afterEvent)
118
- }
124
+ // Emit after event
125
+ const afterEvent = createAfterEvent(action, stateBefore, this.store)
126
+ if (afterEvent) {
127
+ this.eventEmitter.emit(afterEvent)
128
+ }
119
129
 
120
- return true
130
+ return true
131
+ } finally {
132
+ this.store.commitTransaction()
133
+ }
121
134
  }
122
135
 
123
136
  /**
@@ -174,6 +187,9 @@ export class ActionDispatcher {
174
187
  const index = this.middlewares.indexOf(middleware)
175
188
  if (index !== -1) {
176
189
  this.middlewares.splice(index, 1)
190
+ if (!this.middlewares.includes(middleware)) {
191
+ middleware.dispose?.()
192
+ }
177
193
  }
178
194
  }
179
195
 
@@ -373,6 +389,7 @@ export class ActionDispatcher {
373
389
  action.entityType,
374
390
  action.entityId,
375
391
  action.isPersisting,
392
+ action.pessimistic ?? false,
376
393
  )
377
394
  break
378
395
 
@@ -455,7 +472,10 @@ export class ActionDispatcher {
455
472
  * Can inspect/modify actions before execution.
456
473
  * Return false to cancel the action.
457
474
  */
458
- export type ActionMiddleware = (action: Action, store: SnapshotStore) => boolean | void
475
+ export interface ActionMiddleware {
476
+ (action: Action, store: SnapshotStore): boolean | void
477
+ dispose?: () => void
478
+ }
459
479
 
460
480
  /**
461
481
  * Creates a logging middleware for debugging.
@@ -202,6 +202,12 @@ export interface SetPersistingAction {
202
202
  readonly entityType: string
203
203
  readonly entityId: string
204
204
  readonly isPersisting: boolean
205
+ /**
206
+ * Whether this persist is pessimistic. While a pessimistic persist is
207
+ * in-flight, handles present the server baseline instead of the (still dirty)
208
+ * local data. Ignored when isPersisting is false.
209
+ */
210
+ readonly pessimistic?: boolean
205
211
  }
206
212
 
207
213
  // ==================== Error Actions ====================
@@ -428,8 +434,9 @@ export function setPersisting(
428
434
  entityType: string,
429
435
  entityId: string,
430
436
  isPersisting: boolean,
437
+ pessimistic: boolean = false,
431
438
  ): SetPersistingAction {
432
- return { type: 'SET_PERSISTING', entityType, entityId, isPersisting }
439
+ return { type: 'SET_PERSISTING', entityType, entityId, isPersisting, pessimistic }
433
440
  }
434
441
 
435
442
  /**