@contember/bindx 0.1.45 → 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.
- package/dist/core/ActionDispatcher.d.ts +4 -1
- package/dist/core/ActionDispatcher.d.ts.map +1 -1
- package/dist/core/actions.d.ts +7 -1
- package/dist/core/actions.d.ts.map +1 -1
- package/dist/events/EventEmitter.d.ts +13 -1
- package/dist/events/EventEmitter.d.ts.map +1 -1
- package/dist/handles/BaseHandle.d.ts +14 -3
- package/dist/handles/BaseHandle.d.ts.map +1 -1
- package/dist/handles/EntityHandle.d.ts +14 -5
- package/dist/handles/EntityHandle.d.ts.map +1 -1
- package/dist/handles/FieldHandle.d.ts +11 -4
- package/dist/handles/FieldHandle.d.ts.map +1 -1
- package/dist/handles/HasManyListHandle.d.ts +36 -2
- package/dist/handles/HasManyListHandle.d.ts.map +1 -1
- package/dist/handles/HasOneHandle.d.ts +52 -9
- package/dist/handles/HasOneHandle.d.ts.map +1 -1
- package/dist/handles/PlaceholderHandle.d.ts +25 -13
- package/dist/handles/PlaceholderHandle.d.ts.map +1 -1
- package/dist/handles/index.d.ts +1 -1
- package/dist/handles/index.d.ts.map +1 -1
- package/dist/handles/proxyFactory.d.ts +7 -1
- package/dist/handles/proxyFactory.d.ts.map +1 -1
- package/dist/handles/types.d.ts +33 -0
- package/dist/handles/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/persistence/BatchPersister.d.ts +53 -37
- package/dist/persistence/BatchPersister.d.ts.map +1 -1
- package/dist/persistence/ChangeRegistry.d.ts +15 -0
- package/dist/persistence/ChangeRegistry.d.ts.map +1 -1
- package/dist/persistence/MutationCollector.d.ts +106 -3
- package/dist/persistence/MutationCollector.d.ts.map +1 -1
- package/dist/persistence/PersistExecution.d.ts +26 -0
- package/dist/persistence/PersistExecution.d.ts.map +1 -0
- package/dist/persistence/types.d.ts +8 -2
- package/dist/persistence/types.d.ts.map +1 -1
- package/dist/store/EntityMetaStore.d.ts +32 -10
- package/dist/store/EntityMetaStore.d.ts.map +1 -1
- package/dist/store/EntitySnapshotStore.d.ts +50 -3
- package/dist/store/EntitySnapshotStore.d.ts.map +1 -1
- package/dist/store/ErrorStore.d.ts +3 -2
- package/dist/store/ErrorStore.d.ts.map +1 -1
- package/dist/store/HasManyStore.d.ts +214 -0
- package/dist/store/HasManyStore.d.ts.map +1 -0
- package/dist/store/HasOneStore.d.ts +159 -0
- package/dist/store/HasOneStore.d.ts.map +1 -0
- package/dist/store/ReachabilityAnalyzer.d.ts +19 -0
- package/dist/store/ReachabilityAnalyzer.d.ts.map +1 -1
- package/dist/store/RekeyOrchestrator.d.ts +76 -0
- package/dist/store/RekeyOrchestrator.d.ts.map +1 -0
- package/dist/store/RelationEdgeIndex.d.ts +34 -0
- package/dist/store/RelationEdgeIndex.d.ts.map +1 -0
- package/dist/store/RelationStore.d.ts +74 -137
- package/dist/store/RelationStore.d.ts.map +1 -1
- package/dist/store/RootRegistry.d.ts +25 -4
- package/dist/store/RootRegistry.d.ts.map +1 -1
- package/dist/store/SnapshotStore.d.ts +137 -13
- package/dist/store/SnapshotStore.d.ts.map +1 -1
- package/dist/store/SubscriptionManager.d.ts +63 -14
- package/dist/store/SubscriptionManager.d.ts.map +1 -1
- package/dist/store/TouchedStore.d.ts +3 -2
- package/dist/store/TouchedStore.d.ts.map +1 -1
- package/dist/store/relationKey.d.ts +15 -0
- package/dist/store/relationKey.d.ts.map +1 -0
- package/dist/undo/UndoJournal.d.ts +133 -0
- package/dist/undo/UndoJournal.d.ts.map +1 -0
- package/dist/undo/UndoManager.d.ts +25 -82
- package/dist/undo/UndoManager.d.ts.map +1 -1
- package/dist/undo/UnrecordedWriteError.d.ts +16 -0
- package/dist/undo/UnrecordedWriteError.d.ts.map +1 -0
- package/dist/undo/index.d.ts +3 -1
- package/dist/undo/index.d.ts.map +1 -1
- package/dist/undo/rekeyJournalEntry.d.ts +20 -0
- package/dist/undo/rekeyJournalEntry.d.ts.map +1 -0
- package/dist/undo/types.d.ts +0 -48
- package/dist/undo/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/core/ActionDispatcher.ts +56 -36
- package/src/core/actions.ts +8 -1
- package/src/events/EventEmitter.ts +78 -24
- package/src/handles/BaseHandle.ts +19 -2
- package/src/handles/EntityHandle.ts +48 -17
- package/src/handles/FieldHandle.ts +36 -10
- package/src/handles/HasManyListHandle.ts +121 -17
- package/src/handles/HasOneHandle.ts +177 -44
- package/src/handles/PlaceholderHandle.ts +184 -36
- package/src/handles/index.ts +3 -0
- package/src/handles/proxyFactory.ts +27 -0
- package/src/handles/types.ts +39 -0
- package/src/index.ts +4 -4
- package/src/persistence/BatchPersister.ts +810 -446
- package/src/persistence/ChangeRegistry.ts +27 -3
- package/src/persistence/MutationCollector.ts +265 -31
- package/src/persistence/PersistExecution.ts +217 -0
- package/src/persistence/types.ts +8 -2
- package/src/store/EntityMetaStore.ts +89 -45
- package/src/store/EntitySnapshotStore.ts +102 -19
- package/src/store/ErrorStore.ts +6 -5
- package/src/store/HasManyStore.ts +810 -0
- package/src/store/HasOneStore.ts +454 -0
- package/src/store/ReachabilityAnalyzer.ts +37 -0
- package/src/store/RekeyOrchestrator.ts +123 -0
- package/src/store/RelationEdgeIndex.ts +79 -0
- package/src/store/RelationStore.ts +176 -725
- package/src/store/RootRegistry.ts +50 -8
- package/src/store/SnapshotStore.ts +572 -101
- package/src/store/SubscriptionManager.ts +122 -75
- package/src/store/TouchedStore.ts +5 -2
- package/src/store/relationKey.ts +20 -0
- package/src/undo/UndoJournal.ts +258 -0
- package/src/undo/UndoManager.ts +151 -281
- package/src/undo/UnrecordedWriteError.ts +23 -0
- package/src/undo/index.ts +8 -7
- package/src/undo/rekeyJournalEntry.ts +145 -0
- package/src/undo/types.ts +0 -58
- package/dist/core/actionClassification.d.ts +0 -48
- package/dist/core/actionClassification.d.ts.map +0 -1
- package/src/core/actionClassification.ts +0 -199
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { RekeyContext } from '../store/RekeyOrchestrator.js'
|
|
2
|
+
import { createEntitySnapshot } from '../store/snapshots.js'
|
|
3
|
+
import type { EntitySnapshot } from '../store/snapshots.js'
|
|
4
|
+
import type { StoredHasManyState } from '../store/RelationStore.js'
|
|
5
|
+
import type { JournalEntry, JournalCellImage } from './UndoJournal.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Rewrites a journal entry for a temp→persisted rekey so stored cells stay valid
|
|
9
|
+
* after persist:
|
|
10
|
+
* - owner keys move from the temp prefix to the persisted prefix;
|
|
11
|
+
* - embedded id references (snapshot ids, relation currentId/serverId, has-many
|
|
12
|
+
* members) are remapped oldId→newId;
|
|
13
|
+
* - the create is SEALED: an "absent" pre-image of the now-persisted entity (or
|
|
14
|
+
* its owned relations) is dropped, so undo can no longer delete the
|
|
15
|
+
* server-backed row — only later edits to it remain undoable.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Looks up the live server-member ids of a has-many list by its key. Lets the
|
|
19
|
+
* rekey rebase a pre-image when the just-persisted create has become a permanent
|
|
20
|
+
* member of that list.
|
|
21
|
+
*/
|
|
22
|
+
export type LiveServerIdsLookup = (relationKey: string) => Set<string>
|
|
23
|
+
|
|
24
|
+
export function rekeyJournalEntry(
|
|
25
|
+
entry: JournalEntry,
|
|
26
|
+
ctx: RekeyContext,
|
|
27
|
+
liveServerIds: LiveServerIdsLookup,
|
|
28
|
+
): JournalEntry {
|
|
29
|
+
const cells: JournalCellImage[] = []
|
|
30
|
+
for (const cell of entry.cells) {
|
|
31
|
+
const rewritten = rekeyCell(cell, ctx, liveServerIds)
|
|
32
|
+
if (rewritten) cells.push(rewritten)
|
|
33
|
+
}
|
|
34
|
+
return { cells }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function rekeyKey(key: string, ctx: RekeyContext): string {
|
|
38
|
+
if (key === ctx.oldKey) return ctx.newKey
|
|
39
|
+
if (key.startsWith(ctx.oldKeyPrefix)) return ctx.newKeyPrefix + key.slice(ctx.oldKeyPrefix.length)
|
|
40
|
+
return key
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isOwnedByRekeyed(key: string, ctx: RekeyContext): boolean {
|
|
44
|
+
return key === ctx.oldKey || key.startsWith(ctx.oldKeyPrefix)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function rekeyCell(
|
|
48
|
+
cell: JournalCellImage,
|
|
49
|
+
ctx: RekeyContext,
|
|
50
|
+
liveServerIds: LiveServerIdsLookup,
|
|
51
|
+
): JournalCellImage | null {
|
|
52
|
+
// Seal: an absent pre-image of the rekeyed entity (or a relation it owns) would
|
|
53
|
+
// un-create a now server-backed row on undo. Drop it.
|
|
54
|
+
if (!cell.present && isOwnedByRekeyed(cell.key, ctx)) {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (cell.kind === 'entity') {
|
|
59
|
+
const key = rekeyKey(cell.key, ctx)
|
|
60
|
+
if (!cell.snapshot) return { ...cell, key }
|
|
61
|
+
return { ...cell, key, snapshot: rekeySnapshotId(cell.snapshot, ctx) }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (cell.kind === 'relation') {
|
|
65
|
+
const key = rekeyKey(cell.key, ctx)
|
|
66
|
+
if (!cell.state) return { ...cell, key }
|
|
67
|
+
return {
|
|
68
|
+
...cell,
|
|
69
|
+
key,
|
|
70
|
+
state: {
|
|
71
|
+
...cell.state,
|
|
72
|
+
currentId: cell.state.currentId === ctx.oldId ? ctx.newId : cell.state.currentId,
|
|
73
|
+
serverId: cell.state.serverId === ctx.oldId ? ctx.newId : cell.state.serverId,
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const key = rekeyKey(cell.key, ctx)
|
|
79
|
+
if (!cell.state) return { ...cell, key }
|
|
80
|
+
let state = rekeyHasManyState(cell.state, ctx)
|
|
81
|
+
// Membership rebase: when the just-persisted create became a permanent member of
|
|
82
|
+
// this live list, fold it into the (older) pre-image so undo keeps it instead of
|
|
83
|
+
// dropping it. Default order picks it up automatically; an explicit order needs
|
|
84
|
+
// the id appended.
|
|
85
|
+
if (liveServerIds(key).has(ctx.newId) && !state.serverIds.has(ctx.newId)) {
|
|
86
|
+
const serverIds = new Set(state.serverIds)
|
|
87
|
+
serverIds.add(ctx.newId)
|
|
88
|
+
let orderedIds = state.orderedIds
|
|
89
|
+
if (orderedIds && !orderedIds.includes(ctx.newId)) {
|
|
90
|
+
orderedIds = [...orderedIds, ctx.newId]
|
|
91
|
+
}
|
|
92
|
+
state = { ...state, serverIds, orderedIds }
|
|
93
|
+
}
|
|
94
|
+
return { ...cell, key, state }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function rekeySnapshotId(snapshot: EntitySnapshot, ctx: RekeyContext): EntitySnapshot {
|
|
98
|
+
if (snapshot.id !== ctx.oldId) return snapshot
|
|
99
|
+
return createEntitySnapshot(
|
|
100
|
+
ctx.newId,
|
|
101
|
+
snapshot.entityType,
|
|
102
|
+
{ ...(snapshot.data as Record<string, unknown>), id: ctx.newId },
|
|
103
|
+
{ ...(snapshot.serverData as Record<string, unknown>), id: ctx.newId },
|
|
104
|
+
snapshot.version,
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function swapInSet(set: Set<string>, oldId: string, newId: string): Set<string> {
|
|
109
|
+
if (!set.has(oldId)) return set
|
|
110
|
+
const next = new Set(set)
|
|
111
|
+
next.delete(oldId)
|
|
112
|
+
next.add(newId)
|
|
113
|
+
return next
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function rekeyHasManyState(state: StoredHasManyState, ctx: RekeyContext): StoredHasManyState {
|
|
117
|
+
const serverIds = swapInSet(state.serverIds, ctx.oldId, ctx.newId)
|
|
118
|
+
|
|
119
|
+
let orderedIds = state.orderedIds
|
|
120
|
+
if (orderedIds) {
|
|
121
|
+
const idx = orderedIds.indexOf(ctx.oldId)
|
|
122
|
+
if (idx !== -1) {
|
|
123
|
+
orderedIds = [...orderedIds]
|
|
124
|
+
orderedIds[idx] = ctx.newId
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let plannedAdditions = state.plannedAdditions
|
|
129
|
+
const additionKind = plannedAdditions.get(ctx.oldId)
|
|
130
|
+
if (additionKind !== undefined) {
|
|
131
|
+
plannedAdditions = new Map(plannedAdditions)
|
|
132
|
+
plannedAdditions.delete(ctx.oldId)
|
|
133
|
+
plannedAdditions.set(ctx.newId, additionKind)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let plannedRemovals = state.plannedRemovals
|
|
137
|
+
const removalType = plannedRemovals.get(ctx.oldId)
|
|
138
|
+
if (removalType !== undefined) {
|
|
139
|
+
plannedRemovals = new Map(plannedRemovals)
|
|
140
|
+
plannedRemovals.delete(ctx.oldId)
|
|
141
|
+
plannedRemovals.set(ctx.newId, removalType)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { serverIds, orderedIds, plannedRemovals, plannedAdditions, version: state.version }
|
|
145
|
+
}
|
package/src/undo/types.ts
CHANGED
|
@@ -1,58 +1,3 @@
|
|
|
1
|
-
import type { StoreAffectedKeys } from '../core/actionClassification.js'
|
|
2
|
-
import type { EntitySnapshot } from '../store/snapshots.js'
|
|
3
|
-
import type {
|
|
4
|
-
StoredRelationState,
|
|
5
|
-
StoredHasManyState,
|
|
6
|
-
EntityMeta,
|
|
7
|
-
} from '../store/SnapshotStore.js'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Partial snapshot of store state.
|
|
11
|
-
* Contains only the data affected by an action or group of actions.
|
|
12
|
-
*/
|
|
13
|
-
export interface PartialStoreSnapshot {
|
|
14
|
-
/** Entity snapshots (frozen, immutable) */
|
|
15
|
-
entitySnapshots: Map<string, EntitySnapshot>
|
|
16
|
-
/** Has-one relation states */
|
|
17
|
-
relationStates: Map<string, StoredRelationState>
|
|
18
|
-
/** Has-many list states */
|
|
19
|
-
hasManyStates: Map<string, StoredHasManyState>
|
|
20
|
-
/** Entity metadata (existsOnServer, deletion flags) */
|
|
21
|
-
entityMetas: Map<string, EntityMeta>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* A single undo/redo entry.
|
|
26
|
-
* Contains the snapshot of state BEFORE the action(s) were applied.
|
|
27
|
-
*/
|
|
28
|
-
export interface UndoEntry {
|
|
29
|
-
/** Unique identifier for this entry */
|
|
30
|
-
id: string
|
|
31
|
-
/** Optional human-readable label */
|
|
32
|
-
label?: string
|
|
33
|
-
/** Snapshot of state BEFORE the action(s) */
|
|
34
|
-
beforeSnapshot: PartialStoreSnapshot
|
|
35
|
-
/** Keys that were affected (for redo snapshot capture) */
|
|
36
|
-
affectedKeys: StoreAffectedKeys
|
|
37
|
-
/** When this entry was created */
|
|
38
|
-
timestamp: number
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Pending entry during debounce window.
|
|
43
|
-
* Accumulates affected keys while waiting for debounce to complete.
|
|
44
|
-
*/
|
|
45
|
-
export interface PendingUndoEntry {
|
|
46
|
-
/** Snapshot captured before the first action in the group */
|
|
47
|
-
beforeSnapshot: PartialStoreSnapshot
|
|
48
|
-
/** Accumulated affected keys */
|
|
49
|
-
affectedKeys: StoreAffectedKeys
|
|
50
|
-
/** When the first action occurred */
|
|
51
|
-
timestamp: number
|
|
52
|
-
/** Optional label for manual grouping */
|
|
53
|
-
label?: string
|
|
54
|
-
}
|
|
55
|
-
|
|
56
1
|
/**
|
|
57
2
|
* Configuration options for UndoManager.
|
|
58
3
|
*/
|
|
@@ -79,6 +24,3 @@ export interface UndoState {
|
|
|
79
24
|
/** Number of entries in redo stack */
|
|
80
25
|
redoCount: number
|
|
81
26
|
}
|
|
82
|
-
|
|
83
|
-
// Re-export StoreAffectedKeys for convenience
|
|
84
|
-
export type { StoreAffectedKeys } from '../core/actionClassification.js'
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { Action } from './actions.js';
|
|
2
|
-
/**
|
|
3
|
-
* Keys in the store that are affected by an action.
|
|
4
|
-
* Used for partial snapshot capture/restore.
|
|
5
|
-
*/
|
|
6
|
-
export interface StoreAffectedKeys {
|
|
7
|
-
/** Entity keys in format "entityType:id" */
|
|
8
|
-
entityKeys: string[];
|
|
9
|
-
/** Relation keys in format "entityType:id:fieldName" */
|
|
10
|
-
relationKeys: string[];
|
|
11
|
-
/** Has-many keys in format "entityType:id:fieldName" */
|
|
12
|
-
hasManyKeys: string[];
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Checks if an action should be tracked for undo/redo.
|
|
16
|
-
* Server data loads (SET_ENTITY_DATA with isServerData=true) are not tracked.
|
|
17
|
-
*/
|
|
18
|
-
export declare function isTrackableAction(action: Action): boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Checks if an action should be skipped for undo/redo.
|
|
21
|
-
*/
|
|
22
|
-
export declare function isSkippedAction(action: Action): boolean;
|
|
23
|
-
/**
|
|
24
|
-
* Creates an entity key from type and id.
|
|
25
|
-
*/
|
|
26
|
-
export declare function createEntityKey(entityType: string, entityId: string): string;
|
|
27
|
-
/**
|
|
28
|
-
* Creates a relation key from parent type, id and field name.
|
|
29
|
-
*/
|
|
30
|
-
export declare function createRelationKey(entityType: string, entityId: string, fieldName: string): string;
|
|
31
|
-
/**
|
|
32
|
-
* Extracts the store keys affected by an action.
|
|
33
|
-
* Returns entity keys, relation keys, and has-many keys.
|
|
34
|
-
*/
|
|
35
|
-
export declare function getAffectedKeys(action: Action): StoreAffectedKeys;
|
|
36
|
-
/**
|
|
37
|
-
* Merges two StoreAffectedKeys objects, deduplicating keys.
|
|
38
|
-
*/
|
|
39
|
-
export declare function mergeAffectedKeys(target: StoreAffectedKeys, source: StoreAffectedKeys): void;
|
|
40
|
-
/**
|
|
41
|
-
* Creates an empty StoreAffectedKeys object.
|
|
42
|
-
*/
|
|
43
|
-
export declare function createEmptyAffectedKeys(): StoreAffectedKeys;
|
|
44
|
-
/**
|
|
45
|
-
* Checks if StoreAffectedKeys is empty.
|
|
46
|
-
*/
|
|
47
|
-
export declare function isEmptyAffectedKeys(keys: StoreAffectedKeys): boolean;
|
|
48
|
-
//# sourceMappingURL=actionClassification.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"actionClassification.d.ts","sourceRoot":"","sources":["../../src/core/actionClassification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IACjC,4CAA4C;IAC5C,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,wDAAwD;IACxD,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,wDAAwD;IACxD,WAAW,EAAE,MAAM,EAAE,CAAA;CACrB;AAyCD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CASzD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAChC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GACf,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAsDjE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,iBAAiB,GACvB,IAAI,CAgBN;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,iBAAiB,CAM3D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAMpE"}
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import type { Action } from './actions.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Keys in the store that are affected by an action.
|
|
5
|
-
* Used for partial snapshot capture/restore.
|
|
6
|
-
*/
|
|
7
|
-
export interface StoreAffectedKeys {
|
|
8
|
-
/** Entity keys in format "entityType:id" */
|
|
9
|
-
entityKeys: string[]
|
|
10
|
-
/** Relation keys in format "entityType:id:fieldName" */
|
|
11
|
-
relationKeys: string[]
|
|
12
|
-
/** Has-many keys in format "entityType:id:fieldName" */
|
|
13
|
-
hasManyKeys: string[]
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Action types that should be tracked for undo/redo.
|
|
18
|
-
* These are user-initiated data mutations.
|
|
19
|
-
*/
|
|
20
|
-
const TRACKABLE_ACTION_TYPES = new Set<Action['type']>([
|
|
21
|
-
'SET_FIELD',
|
|
22
|
-
'SET_ENTITY_DATA',
|
|
23
|
-
'SET_PLACEHOLDER_DATA',
|
|
24
|
-
'CONNECT_RELATION',
|
|
25
|
-
'DISCONNECT_RELATION',
|
|
26
|
-
'DELETE_RELATION',
|
|
27
|
-
'RESET_ENTITY',
|
|
28
|
-
'RESET_RELATION',
|
|
29
|
-
'CONNECT_TO_LIST',
|
|
30
|
-
'ADD_TO_LIST',
|
|
31
|
-
'REMOVE_FROM_LIST',
|
|
32
|
-
'MOVE_IN_LIST',
|
|
33
|
-
])
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Action types that should be skipped for undo/redo.
|
|
37
|
-
* These are operational, commit, or error actions.
|
|
38
|
-
*/
|
|
39
|
-
const SKIP_ACTION_TYPES = new Set<Action['type']>([
|
|
40
|
-
'SET_LOAD_STATE',
|
|
41
|
-
'SET_PERSISTING',
|
|
42
|
-
'COMMIT_ENTITY',
|
|
43
|
-
'COMMIT_RELATION',
|
|
44
|
-
// Error actions are skipped as errors are ephemeral state
|
|
45
|
-
'ADD_FIELD_ERROR',
|
|
46
|
-
'CLEAR_FIELD_ERRORS',
|
|
47
|
-
'ADD_ENTITY_ERROR',
|
|
48
|
-
'CLEAR_ENTITY_ERRORS',
|
|
49
|
-
'ADD_RELATION_ERROR',
|
|
50
|
-
'CLEAR_RELATION_ERRORS',
|
|
51
|
-
'CLEAR_ALL_SERVER_ERRORS',
|
|
52
|
-
'CLEAR_ALL_ERRORS',
|
|
53
|
-
])
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Checks if an action should be tracked for undo/redo.
|
|
57
|
-
* Server data loads (SET_ENTITY_DATA with isServerData=true) are not tracked.
|
|
58
|
-
*/
|
|
59
|
-
export function isTrackableAction(action: Action): boolean {
|
|
60
|
-
if (!TRACKABLE_ACTION_TYPES.has(action.type)) {
|
|
61
|
-
return false
|
|
62
|
-
}
|
|
63
|
-
// Server data loads are not user-initiated mutations
|
|
64
|
-
if (action.type === 'SET_ENTITY_DATA' && action.isServerData) {
|
|
65
|
-
return false
|
|
66
|
-
}
|
|
67
|
-
return true
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Checks if an action should be skipped for undo/redo.
|
|
72
|
-
*/
|
|
73
|
-
export function isSkippedAction(action: Action): boolean {
|
|
74
|
-
return SKIP_ACTION_TYPES.has(action.type)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Creates an entity key from type and id.
|
|
79
|
-
*/
|
|
80
|
-
export function createEntityKey(entityType: string, entityId: string): string {
|
|
81
|
-
return `${entityType}:${entityId}`
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Creates a relation key from parent type, id and field name.
|
|
86
|
-
*/
|
|
87
|
-
export function createRelationKey(
|
|
88
|
-
entityType: string,
|
|
89
|
-
entityId: string,
|
|
90
|
-
fieldName: string,
|
|
91
|
-
): string {
|
|
92
|
-
return `${entityType}:${entityId}:${fieldName}`
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Extracts the store keys affected by an action.
|
|
97
|
-
* Returns entity keys, relation keys, and has-many keys.
|
|
98
|
-
*/
|
|
99
|
-
export function getAffectedKeys(action: Action): StoreAffectedKeys {
|
|
100
|
-
const entityKeys: string[] = []
|
|
101
|
-
const relationKeys: string[] = []
|
|
102
|
-
const hasManyKeys: string[] = []
|
|
103
|
-
|
|
104
|
-
switch (action.type) {
|
|
105
|
-
case 'SET_FIELD':
|
|
106
|
-
case 'SET_ENTITY_DATA':
|
|
107
|
-
case 'REFRESH_SERVER_DATA':
|
|
108
|
-
case 'RESET_ENTITY':
|
|
109
|
-
case 'COMMIT_ENTITY':
|
|
110
|
-
entityKeys.push(createEntityKey(action.entityType, action.entityId))
|
|
111
|
-
break
|
|
112
|
-
|
|
113
|
-
case 'CONNECT_RELATION':
|
|
114
|
-
case 'DISCONNECT_RELATION':
|
|
115
|
-
case 'DELETE_RELATION':
|
|
116
|
-
case 'RESET_RELATION':
|
|
117
|
-
case 'COMMIT_RELATION':
|
|
118
|
-
case 'SET_PLACEHOLDER_DATA':
|
|
119
|
-
relationKeys.push(
|
|
120
|
-
createRelationKey(action.entityType, action.entityId, action.fieldName),
|
|
121
|
-
)
|
|
122
|
-
break
|
|
123
|
-
|
|
124
|
-
case 'CONNECT_TO_LIST':
|
|
125
|
-
case 'ADD_TO_LIST':
|
|
126
|
-
case 'REMOVE_FROM_LIST':
|
|
127
|
-
case 'MOVE_IN_LIST':
|
|
128
|
-
hasManyKeys.push(
|
|
129
|
-
createRelationKey(action.entityType, action.entityId, action.fieldName),
|
|
130
|
-
)
|
|
131
|
-
break
|
|
132
|
-
|
|
133
|
-
case 'SET_LOAD_STATE':
|
|
134
|
-
case 'SET_PERSISTING':
|
|
135
|
-
case 'ADD_FIELD_ERROR':
|
|
136
|
-
case 'CLEAR_FIELD_ERRORS':
|
|
137
|
-
case 'ADD_ENTITY_ERROR':
|
|
138
|
-
case 'CLEAR_ENTITY_ERRORS':
|
|
139
|
-
case 'ADD_RELATION_ERROR':
|
|
140
|
-
case 'CLEAR_RELATION_ERRORS':
|
|
141
|
-
case 'CLEAR_ALL_SERVER_ERRORS':
|
|
142
|
-
case 'CLEAR_ALL_ERRORS':
|
|
143
|
-
// These don't affect undo-able state
|
|
144
|
-
break
|
|
145
|
-
|
|
146
|
-
default: {
|
|
147
|
-
const _exhaustive: never = action
|
|
148
|
-
throw new Error(`Unknown action type: ${(_exhaustive as Action).type}`)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return { entityKeys, relationKeys, hasManyKeys }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Merges two StoreAffectedKeys objects, deduplicating keys.
|
|
157
|
-
*/
|
|
158
|
-
export function mergeAffectedKeys(
|
|
159
|
-
target: StoreAffectedKeys,
|
|
160
|
-
source: StoreAffectedKeys,
|
|
161
|
-
): void {
|
|
162
|
-
for (const key of source.entityKeys) {
|
|
163
|
-
if (!target.entityKeys.includes(key)) {
|
|
164
|
-
target.entityKeys.push(key)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
for (const key of source.relationKeys) {
|
|
168
|
-
if (!target.relationKeys.includes(key)) {
|
|
169
|
-
target.relationKeys.push(key)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
for (const key of source.hasManyKeys) {
|
|
173
|
-
if (!target.hasManyKeys.includes(key)) {
|
|
174
|
-
target.hasManyKeys.push(key)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Creates an empty StoreAffectedKeys object.
|
|
181
|
-
*/
|
|
182
|
-
export function createEmptyAffectedKeys(): StoreAffectedKeys {
|
|
183
|
-
return {
|
|
184
|
-
entityKeys: [],
|
|
185
|
-
relationKeys: [],
|
|
186
|
-
hasManyKeys: [],
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Checks if StoreAffectedKeys is empty.
|
|
192
|
-
*/
|
|
193
|
-
export function isEmptyAffectedKeys(keys: StoreAffectedKeys): boolean {
|
|
194
|
-
return (
|
|
195
|
-
keys.entityKeys.length === 0 &&
|
|
196
|
-
keys.relationKeys.length === 0 &&
|
|
197
|
-
keys.hasManyKeys.length === 0
|
|
198
|
-
)
|
|
199
|
-
}
|