@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.
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 +121 -17
  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
@@ -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
- }