@contember/bindx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/dist/adapter/ContemberAdapter.d.ts +40 -0
  2. package/dist/adapter/ContemberAdapter.d.ts.map +1 -0
  3. package/dist/adapter/MockAdapter.d.ts +81 -0
  4. package/dist/adapter/MockAdapter.d.ts.map +1 -0
  5. package/dist/adapter/MockQueryEngine.d.ts +43 -0
  6. package/dist/adapter/MockQueryEngine.d.ts.map +1 -0
  7. package/dist/adapter/index.d.ts +5 -0
  8. package/dist/adapter/index.d.ts.map +1 -0
  9. package/dist/adapter/types.d.ts +184 -0
  10. package/dist/adapter/types.d.ts.map +1 -0
  11. package/dist/brand/ComponentBrand.d.ts +25 -0
  12. package/dist/brand/ComponentBrand.d.ts.map +1 -0
  13. package/dist/contember/index.d.ts +10 -0
  14. package/dist/contember/index.d.ts.map +1 -0
  15. package/dist/core/ActionDispatcher.d.ts +64 -0
  16. package/dist/core/ActionDispatcher.d.ts.map +1 -0
  17. package/dist/core/Disposable.d.ts +57 -0
  18. package/dist/core/Disposable.d.ts.map +1 -0
  19. package/dist/core/EntityLoader.d.ts +80 -0
  20. package/dist/core/EntityLoader.d.ts.map +1 -0
  21. package/dist/core/PersistenceManager.d.ts +126 -0
  22. package/dist/core/PersistenceManager.d.ts.map +1 -0
  23. package/dist/core/SelectionResolver.d.ts +21 -0
  24. package/dist/core/SelectionResolver.d.ts.map +1 -0
  25. package/dist/core/actionClassification.d.ts +48 -0
  26. package/dist/core/actionClassification.d.ts.map +1 -0
  27. package/dist/core/actions.d.ts +301 -0
  28. package/dist/core/actions.d.ts.map +1 -0
  29. package/dist/core/index.d.ts +4 -0
  30. package/dist/core/index.d.ts.map +1 -0
  31. package/dist/dataview/filterHandlers.d.ts +18 -0
  32. package/dist/dataview/filterHandlers.d.ts.map +1 -0
  33. package/dist/dataview/index.d.ts +3 -0
  34. package/dist/dataview/index.d.ts.map +1 -0
  35. package/dist/dataview/types.d.ts +164 -0
  36. package/dist/dataview/types.d.ts.map +1 -0
  37. package/dist/errors/UnfetchedFieldError.d.ts +11 -0
  38. package/dist/errors/UnfetchedFieldError.d.ts.map +1 -0
  39. package/dist/errors/errorPathResolver.d.ts +52 -0
  40. package/dist/errors/errorPathResolver.d.ts.map +1 -0
  41. package/dist/errors/index.d.ts +5 -0
  42. package/dist/errors/index.d.ts.map +1 -0
  43. package/dist/errors/pathMapper.d.ts +42 -0
  44. package/dist/errors/pathMapper.d.ts.map +1 -0
  45. package/dist/errors/types.d.ts +131 -0
  46. package/dist/errors/types.d.ts.map +1 -0
  47. package/dist/events/EventEmitter.d.ts +97 -0
  48. package/dist/events/EventEmitter.d.ts.map +1 -0
  49. package/dist/events/eventFactory.d.ts +40 -0
  50. package/dist/events/eventFactory.d.ts.map +1 -0
  51. package/dist/events/index.d.ts +15 -0
  52. package/dist/events/index.d.ts.map +1 -0
  53. package/dist/events/types.d.ts +264 -0
  54. package/dist/events/types.d.ts.map +1 -0
  55. package/dist/handles/BaseHandle.d.ts +64 -0
  56. package/dist/handles/BaseHandle.d.ts.map +1 -0
  57. package/dist/handles/EntityHandle.d.ts +208 -0
  58. package/dist/handles/EntityHandle.d.ts.map +1 -0
  59. package/dist/handles/FieldHandle.d.ts +111 -0
  60. package/dist/handles/FieldHandle.d.ts.map +1 -0
  61. package/dist/handles/HasManyListHandle.d.ts +166 -0
  62. package/dist/handles/HasManyListHandle.d.ts.map +1 -0
  63. package/dist/handles/HasOneHandle.d.ts +168 -0
  64. package/dist/handles/HasOneHandle.d.ts.map +1 -0
  65. package/dist/handles/PlaceholderHandle.d.ts +117 -0
  66. package/dist/handles/PlaceholderHandle.d.ts.map +1 -0
  67. package/dist/handles/index.d.ts +8 -0
  68. package/dist/handles/index.d.ts.map +1 -0
  69. package/dist/handles/proxyFactory.d.ts +66 -0
  70. package/dist/handles/proxyFactory.d.ts.map +1 -0
  71. package/dist/handles/types.d.ts +430 -0
  72. package/dist/handles/types.d.ts.map +1 -0
  73. package/dist/index.d.ts +53 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/internal.d.ts +11 -0
  76. package/dist/internal.d.ts.map +1 -0
  77. package/dist/persistence/BatchPersister.d.ts +142 -0
  78. package/dist/persistence/BatchPersister.d.ts.map +1 -0
  79. package/dist/persistence/ChangeRegistry.d.ts +87 -0
  80. package/dist/persistence/ChangeRegistry.d.ts.map +1 -0
  81. package/dist/persistence/ContemberSchemaMutationAdapter.d.ts +16 -0
  82. package/dist/persistence/ContemberSchemaMutationAdapter.d.ts.map +1 -0
  83. package/dist/persistence/MutationCollector.d.ts +96 -0
  84. package/dist/persistence/MutationCollector.d.ts.map +1 -0
  85. package/dist/persistence/MutationSchemaProvider.d.ts +30 -0
  86. package/dist/persistence/MutationSchemaProvider.d.ts.map +1 -0
  87. package/dist/persistence/index.d.ts +7 -0
  88. package/dist/persistence/index.d.ts.map +1 -0
  89. package/dist/persistence/types.d.ts +189 -0
  90. package/dist/persistence/types.d.ts.map +1 -0
  91. package/dist/roles/RoleSchemaRegistry.d.ts +77 -0
  92. package/dist/roles/RoleSchemaRegistry.d.ts.map +1 -0
  93. package/dist/roles/index.d.ts +11 -0
  94. package/dist/roles/index.d.ts.map +1 -0
  95. package/dist/roles/types.d.ts +242 -0
  96. package/dist/roles/types.d.ts.map +1 -0
  97. package/dist/schema/ContemberSchema.d.ts +98 -0
  98. package/dist/schema/ContemberSchema.d.ts.map +1 -0
  99. package/dist/schema/SchemaLoader.d.ts +28 -0
  100. package/dist/schema/SchemaLoader.d.ts.map +1 -0
  101. package/dist/schema/SchemaRegistry.d.ts +95 -0
  102. package/dist/schema/SchemaRegistry.d.ts.map +1 -0
  103. package/dist/schema/index.d.ts +6 -0
  104. package/dist/schema/index.d.ts.map +1 -0
  105. package/dist/schema/roles.d.ts +62 -0
  106. package/dist/schema/roles.d.ts.map +1 -0
  107. package/dist/schema/types.d.ts +179 -0
  108. package/dist/schema/types.d.ts.map +1 -0
  109. package/dist/selection/SelectionMetaCollector.d.ts +32 -0
  110. package/dist/selection/SelectionMetaCollector.d.ts.map +1 -0
  111. package/dist/selection/SelectionScope.d.ts +77 -0
  112. package/dist/selection/SelectionScope.d.ts.map +1 -0
  113. package/dist/selection/buildQuery.d.ts +45 -0
  114. package/dist/selection/buildQuery.d.ts.map +1 -0
  115. package/dist/selection/createFragment.d.ts +29 -0
  116. package/dist/selection/createFragment.d.ts.map +1 -0
  117. package/dist/selection/createSelectionBuilder.d.ts +18 -0
  118. package/dist/selection/createSelectionBuilder.d.ts.map +1 -0
  119. package/dist/selection/index.d.ts +8 -0
  120. package/dist/selection/index.d.ts.map +1 -0
  121. package/dist/selection/queryTypes.d.ts +84 -0
  122. package/dist/selection/queryTypes.d.ts.map +1 -0
  123. package/dist/selection/types.d.ts +238 -0
  124. package/dist/selection/types.d.ts.map +1 -0
  125. package/dist/store/DirtyTracker.d.ts +26 -0
  126. package/dist/store/DirtyTracker.d.ts.map +1 -0
  127. package/dist/store/EntityMetaStore.d.ts +52 -0
  128. package/dist/store/EntityMetaStore.d.ts.map +1 -0
  129. package/dist/store/EntitySnapshotStore.d.ts +63 -0
  130. package/dist/store/EntitySnapshotStore.d.ts.map +1 -0
  131. package/dist/store/ErrorStore.d.ts +82 -0
  132. package/dist/store/ErrorStore.d.ts.map +1 -0
  133. package/dist/store/RelationStore.d.ts +175 -0
  134. package/dist/store/RelationStore.d.ts.map +1 -0
  135. package/dist/store/SnapshotStore.d.ts +143 -0
  136. package/dist/store/SnapshotStore.d.ts.map +1 -0
  137. package/dist/store/SubscriptionManager.d.ts +90 -0
  138. package/dist/store/SubscriptionManager.d.ts.map +1 -0
  139. package/dist/store/TouchedStore.d.ts +24 -0
  140. package/dist/store/TouchedStore.d.ts.map +1 -0
  141. package/dist/store/entityId.d.ts +29 -0
  142. package/dist/store/entityId.d.ts.map +1 -0
  143. package/dist/store/index.d.ts +1 -0
  144. package/dist/store/index.d.ts.map +1 -0
  145. package/dist/store/snapshots.d.ts +144 -0
  146. package/dist/store/snapshots.d.ts.map +1 -0
  147. package/dist/undo/UndoManager.d.ts +120 -0
  148. package/dist/undo/UndoManager.d.ts.map +1 -0
  149. package/dist/undo/index.d.ts +3 -0
  150. package/dist/undo/index.d.ts.map +1 -0
  151. package/dist/undo/types.d.ts +74 -0
  152. package/dist/undo/types.d.ts.map +1 -0
  153. package/dist/utils/aliasGenerator.d.ts +25 -0
  154. package/dist/utils/aliasGenerator.d.ts.map +1 -0
  155. package/dist/utils/deepEqual.d.ts +6 -0
  156. package/dist/utils/deepEqual.d.ts.map +1 -0
  157. package/dist/utils/index.d.ts +3 -0
  158. package/dist/utils/index.d.ts.map +1 -0
  159. package/package.json +29 -0
  160. package/src/adapter/ContemberAdapter.ts +239 -0
  161. package/src/adapter/MockAdapter.ts +394 -0
  162. package/src/adapter/MockQueryEngine.ts +236 -0
  163. package/src/adapter/index.ts +21 -0
  164. package/src/adapter/types.ts +199 -0
  165. package/src/brand/ComponentBrand.ts +28 -0
  166. package/src/contember/index.ts +17 -0
  167. package/src/core/ActionDispatcher.ts +450 -0
  168. package/src/core/Disposable.ts +126 -0
  169. package/src/core/EntityLoader.ts +169 -0
  170. package/src/core/SelectionResolver.ts +78 -0
  171. package/src/core/actionClassification.ts +196 -0
  172. package/src/core/actions.ts +491 -0
  173. package/src/core/index.ts +20 -0
  174. package/src/dataview/filterHandlers.ts +443 -0
  175. package/src/dataview/index.ts +36 -0
  176. package/src/dataview/types.ts +220 -0
  177. package/src/errors/UnfetchedFieldError.ts +18 -0
  178. package/src/errors/errorPathResolver.ts +239 -0
  179. package/src/errors/index.ts +40 -0
  180. package/src/errors/pathMapper.ts +40 -0
  181. package/src/errors/types.ts +223 -0
  182. package/src/events/EventEmitter.ts +447 -0
  183. package/src/events/eventFactory.ts +430 -0
  184. package/src/events/index.ts +89 -0
  185. package/src/events/types.ts +374 -0
  186. package/src/handles/BaseHandle.ts +100 -0
  187. package/src/handles/EntityHandle.ts +538 -0
  188. package/src/handles/FieldHandle.ts +268 -0
  189. package/src/handles/HasManyListHandle.ts +500 -0
  190. package/src/handles/HasOneHandle.ts +500 -0
  191. package/src/handles/PlaceholderHandle.ts +321 -0
  192. package/src/handles/index.ts +37 -0
  193. package/src/handles/proxyFactory.ts +218 -0
  194. package/src/handles/types.ts +743 -0
  195. package/src/index.ts +384 -0
  196. package/src/internal.ts +19 -0
  197. package/src/persistence/BatchPersister.ts +911 -0
  198. package/src/persistence/ChangeRegistry.ts +150 -0
  199. package/src/persistence/ContemberSchemaMutationAdapter.ts +57 -0
  200. package/src/persistence/MutationCollector.ts +567 -0
  201. package/src/persistence/MutationSchemaProvider.ts +33 -0
  202. package/src/persistence/index.ts +23 -0
  203. package/src/persistence/types.ts +220 -0
  204. package/src/schema/ContemberSchema.ts +132 -0
  205. package/src/schema/SchemaLoader.ts +93 -0
  206. package/src/schema/SchemaRegistry.ts +286 -0
  207. package/src/schema/index.ts +51 -0
  208. package/src/schema/roles.ts +83 -0
  209. package/src/schema/types.ts +238 -0
  210. package/src/selection/SelectionMetaCollector.ts +122 -0
  211. package/src/selection/SelectionScope.ts +208 -0
  212. package/src/selection/buildQuery.ts +134 -0
  213. package/src/selection/createFragment.ts +50 -0
  214. package/src/selection/createSelectionBuilder.ts +283 -0
  215. package/src/selection/index.ts +31 -0
  216. package/src/selection/queryTypes.ts +135 -0
  217. package/src/selection/types.ts +320 -0
  218. package/src/store/DirtyTracker.ts +117 -0
  219. package/src/store/EntityMetaStore.ts +143 -0
  220. package/src/store/EntitySnapshotStore.ts +260 -0
  221. package/src/store/ErrorStore.ts +251 -0
  222. package/src/store/RelationStore.ts +696 -0
  223. package/src/store/SnapshotStore.ts +765 -0
  224. package/src/store/SubscriptionManager.ts +241 -0
  225. package/src/store/TouchedStore.ts +42 -0
  226. package/src/store/entityId.ts +43 -0
  227. package/src/store/index.ts +2 -0
  228. package/src/store/snapshots.ts +203 -0
  229. package/src/undo/UndoManager.ts +418 -0
  230. package/src/undo/index.ts +9 -0
  231. package/src/undo/types.ts +84 -0
  232. package/src/utils/aliasGenerator.ts +56 -0
  233. package/src/utils/deepEqual.ts +33 -0
  234. package/src/utils/index.ts +2 -0
@@ -0,0 +1,765 @@
1
+ import type { EntitySnapshot, LoadStatus } from './snapshots.js'
2
+ import type { FieldError } from '../errors/types.js'
3
+ import { SubscriptionManager, type SnapshotVersionBumper } from './SubscriptionManager.js'
4
+ import { ErrorStore } from './ErrorStore.js'
5
+ import {
6
+ RelationStore,
7
+ type HasManyRemovalType,
8
+ type StoredHasManyState,
9
+ type StoredRelationState,
10
+ } from './RelationStore.js'
11
+ import { EntityMetaStore, type EntityMeta } from './EntityMetaStore.js'
12
+ import { TouchedStore } from './TouchedStore.js'
13
+ import { generateTempId } from './entityId.js'
14
+ import { DirtyTracker } from './DirtyTracker.js'
15
+ import { EntitySnapshotStore } from './EntitySnapshotStore.js'
16
+
17
+ export type { HasManyRemovalType, StoredHasManyState, StoredRelationState } from './RelationStore.js'
18
+ export type { EntityMeta } from './EntityMetaStore.js'
19
+ export { isTempId, isPlaceholderId, isPersistedId, generatePlaceholderId } from './entityId.js'
20
+
21
+ type Subscriber = () => void
22
+
23
+ /**
24
+ * SnapshotStore manages immutable snapshots for React integration.
25
+ *
26
+ * Key design principles:
27
+ * - All data is stored as immutable, frozen objects
28
+ * - Changes create new snapshot instances (new references)
29
+ * - Provides subscribe/getSnapshot interface for useSyncExternalStore
30
+ * - Entity-level subscriptions for fine-grained reactivity
31
+ *
32
+ * Composed of:
33
+ * - EntitySnapshotStore — entity snapshot CRUD
34
+ * - SubscriptionManager — subscription tracking and notification
35
+ * - ErrorStore — field/entity/relation error tracking
36
+ * - RelationStore — has-one / has-many relation state management
37
+ * - EntityMetaStore — entity metadata, load state, persisting, temp ID mapping
38
+ * - TouchedStore — field touched state tracking
39
+ */
40
+ export class SnapshotStore implements SnapshotVersionBumper {
41
+ private readonly entitySnapshots = new EntitySnapshotStore()
42
+ private readonly subscriptions = new SubscriptionManager()
43
+ private readonly errors = new ErrorStore()
44
+ private readonly relations = new RelationStore()
45
+ private readonly meta = new EntityMetaStore()
46
+ private readonly touched = new TouchedStore()
47
+ private readonly dirtyTracker: DirtyTracker
48
+
49
+ constructor() {
50
+ this.dirtyTracker = new DirtyTracker(this.entitySnapshots, this.meta, this.relations)
51
+ }
52
+
53
+ // ==================== Key Generation ====================
54
+
55
+ private getEntityKey(entityType: string, id: string): string {
56
+ return `${entityType}:${id}`
57
+ }
58
+
59
+ private getRelationKey(parentType: string, parentId: string, fieldName: string): string {
60
+ return `${parentType}:${parentId}:${fieldName}`
61
+ }
62
+
63
+ // ==================== SnapshotVersionBumper ====================
64
+
65
+ bumpEntitySnapshotVersion(key: string): void {
66
+ this.entitySnapshots.bumpVersion(key)
67
+ }
68
+
69
+ // ==================== Notification Helpers ====================
70
+
71
+ private notifyEntitySubscribers(key: string): void {
72
+ this.subscriptions.notifyEntitySubscribers(key, this)
73
+ }
74
+
75
+ private notifyRelationSubscribers(relationKey: string): void {
76
+ const parts = relationKey.split(':')
77
+ if (parts.length >= 2) {
78
+ const entityKey = `${parts[0]}:${parts[1]}`
79
+ this.subscriptions.notifyRelationSubscribers(relationKey, entityKey, this)
80
+ }
81
+ }
82
+
83
+ // ==================== Entity Snapshots ====================
84
+
85
+ getEntitySnapshot<T extends object>(entityType: string, id: string): EntitySnapshot<T> | undefined {
86
+ const key = this.getEntityKey(entityType, id)
87
+ return this.entitySnapshots.get(key) as EntitySnapshot<T> | undefined
88
+ }
89
+
90
+ hasEntity(entityType: string, id: string): boolean {
91
+ const key = this.getEntityKey(entityType, id)
92
+ return this.entitySnapshots.has(key)
93
+ }
94
+
95
+ setEntityData<T extends object>(
96
+ entityType: string,
97
+ id: string,
98
+ data: T,
99
+ isServerData: boolean = false,
100
+ skipNotify: boolean = false,
101
+ ): EntitySnapshot<T> {
102
+ const key = this.getEntityKey(entityType, id)
103
+ const newSnapshot = this.entitySnapshots.setData(key, id, entityType, data, isServerData)
104
+
105
+ if (isServerData) {
106
+ this.meta.setExistsOnServer(key, true)
107
+ }
108
+
109
+ if (!skipNotify) {
110
+ this.notifyEntitySubscribers(key)
111
+ }
112
+
113
+ return newSnapshot as EntitySnapshot<T>
114
+ }
115
+
116
+ updateEntityFields<T extends object>(
117
+ entityType: string,
118
+ id: string,
119
+ updates: Partial<T>,
120
+ ): EntitySnapshot<T> | undefined {
121
+ const key = this.getEntityKey(entityType, id)
122
+ const result = this.entitySnapshots.updateFields<T>(key, updates)
123
+ if (result) {
124
+ this.notifyEntitySubscribers(key)
125
+ }
126
+ return result
127
+ }
128
+
129
+ setFieldValue(
130
+ entityType: string,
131
+ id: string,
132
+ fieldPath: string[],
133
+ value: unknown,
134
+ ): void {
135
+ const key = this.getEntityKey(entityType, id)
136
+ if (this.entitySnapshots.setFieldValue(key, fieldPath, value)) {
137
+ this.notifyEntitySubscribers(key)
138
+ }
139
+ }
140
+
141
+ commitEntity(entityType: string, id: string): void {
142
+ const key = this.getEntityKey(entityType, id)
143
+ this.entitySnapshots.commit(key)
144
+ this.notifyEntitySubscribers(key)
145
+ }
146
+
147
+ resetEntity(entityType: string, id: string): void {
148
+ const key = this.getEntityKey(entityType, id)
149
+ this.entitySnapshots.reset(key)
150
+ this.notifyEntitySubscribers(key)
151
+ }
152
+
153
+ removeEntity(entityType: string, id: string): void {
154
+ const key = this.getEntityKey(entityType, id)
155
+ this.entitySnapshots.remove(key)
156
+ this.meta.clearLoadState(key)
157
+ this.notifyEntitySubscribers(key)
158
+ }
159
+
160
+ // ==================== Load State (delegated to EntityMetaStore) ====================
161
+
162
+ getLoadState(entityType: string, id: string): { status: LoadStatus; error?: FieldError } | undefined {
163
+ const key = this.getEntityKey(entityType, id)
164
+ return this.meta.getLoadState(key)
165
+ }
166
+
167
+ setLoadState(entityType: string, id: string, status: LoadStatus, error?: FieldError): void {
168
+ const key = this.getEntityKey(entityType, id)
169
+ this.meta.setLoadState(key, status, error)
170
+ this.notifyEntitySubscribers(key)
171
+ }
172
+
173
+ // ==================== Entity Meta (delegated to EntityMetaStore) ====================
174
+
175
+ getEntityMeta(entityType: string, id: string): EntityMeta | undefined {
176
+ const key = this.getEntityKey(entityType, id)
177
+ return this.meta.getEntityMeta(key)
178
+ }
179
+
180
+ setExistsOnServer(entityType: string, id: string, existsOnServer: boolean): void {
181
+ const key = this.getEntityKey(entityType, id)
182
+ this.meta.setExistsOnServer(key, existsOnServer)
183
+ this.notifyEntitySubscribers(key)
184
+ }
185
+
186
+ existsOnServer(entityType: string, id: string): boolean {
187
+ const key = this.getEntityKey(entityType, id)
188
+ return this.meta.existsOnServer(key)
189
+ }
190
+
191
+ scheduleForDeletion(entityType: string, id: string): void {
192
+ const key = this.getEntityKey(entityType, id)
193
+ this.meta.scheduleForDeletion(key)
194
+ this.notifyEntitySubscribers(key)
195
+ }
196
+
197
+ unscheduleForDeletion(entityType: string, id: string): void {
198
+ const key = this.getEntityKey(entityType, id)
199
+ this.meta.unscheduleForDeletion(key)
200
+ this.notifyEntitySubscribers(key)
201
+ }
202
+
203
+ isScheduledForDeletion(entityType: string, id: string): boolean {
204
+ const key = this.getEntityKey(entityType, id)
205
+ return this.meta.isScheduledForDeletion(key)
206
+ }
207
+
208
+ // ==================== Create Mode (Temp ID Management) ====================
209
+
210
+ createEntity(entityType: string, initialData?: Record<string, unknown>): string {
211
+ const tempId = generateTempId()
212
+ const data = { id: tempId, ...initialData }
213
+
214
+ this.setEntityData(entityType, tempId, data, false)
215
+ this.setExistsOnServer(entityType, tempId, false)
216
+
217
+ return tempId
218
+ }
219
+
220
+ mapTempIdToPersistedId(entityType: string, tempId: string, persistedId: string): void {
221
+ const key = this.getEntityKey(entityType, tempId)
222
+ this.meta.mapTempIdToPersistedId(key, persistedId)
223
+ this.notifyEntitySubscribers(key)
224
+ }
225
+
226
+ getPersistedId(entityType: string, id: string): string | null {
227
+ const key = this.getEntityKey(entityType, id)
228
+ return this.meta.getPersistedId(key, id)
229
+ }
230
+
231
+ isNewEntity(entityType: string, id: string): boolean {
232
+ const key = this.getEntityKey(entityType, id)
233
+ return this.meta.isNewEntity(key, id)
234
+ }
235
+
236
+ // ==================== Has-Many State (delegated to RelationStore) ====================
237
+
238
+ getOrCreateHasMany(
239
+ parentType: string,
240
+ parentId: string,
241
+ fieldName: string,
242
+ serverIds?: string[],
243
+ alias?: string,
244
+ ): StoredHasManyState {
245
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
246
+ return this.relations.getOrCreateHasMany(key, serverIds)
247
+ }
248
+
249
+ getHasMany(
250
+ parentType: string,
251
+ parentId: string,
252
+ fieldName: string,
253
+ alias?: string,
254
+ ): StoredHasManyState | undefined {
255
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
256
+ return this.relations.getHasMany(key)
257
+ }
258
+
259
+ setHasManyServerIds(
260
+ parentType: string,
261
+ parentId: string,
262
+ fieldName: string,
263
+ serverIds: string[],
264
+ alias?: string,
265
+ ): void {
266
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
267
+ this.relations.setHasManyServerIds(key, serverIds)
268
+ this.notifyRelationSubscribers(key)
269
+ }
270
+
271
+ planHasManyRemoval(
272
+ parentType: string,
273
+ parentId: string,
274
+ fieldName: string,
275
+ itemId: string,
276
+ type: HasManyRemovalType,
277
+ alias?: string,
278
+ ): void {
279
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
280
+ this.relations.planHasManyRemoval(key, itemId, type)
281
+ this.notifyRelationSubscribers(key)
282
+ }
283
+
284
+ cancelHasManyRemoval(
285
+ parentType: string,
286
+ parentId: string,
287
+ fieldName: string,
288
+ itemId: string,
289
+ alias?: string,
290
+ ): void {
291
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
292
+ this.relations.cancelHasManyRemoval(key, itemId)
293
+ this.notifyRelationSubscribers(key)
294
+ }
295
+
296
+ getHasManyPlannedRemovals(
297
+ parentType: string,
298
+ parentId: string,
299
+ fieldName: string,
300
+ alias?: string,
301
+ ): Map<string, HasManyRemovalType> | undefined {
302
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
303
+ return this.relations.getHasMany(key)?.plannedRemovals
304
+ }
305
+
306
+ planHasManyConnection(
307
+ parentType: string,
308
+ parentId: string,
309
+ fieldName: string,
310
+ itemId: string,
311
+ alias?: string,
312
+ ): void {
313
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
314
+ this.relations.planHasManyConnection(key, itemId)
315
+ this.notifyRelationSubscribers(key)
316
+ }
317
+
318
+ cancelHasManyConnection(
319
+ parentType: string,
320
+ parentId: string,
321
+ fieldName: string,
322
+ itemId: string,
323
+ alias?: string,
324
+ ): void {
325
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
326
+ this.relations.cancelHasManyConnection(key, itemId)
327
+ this.notifyRelationSubscribers(key)
328
+ }
329
+
330
+ getHasManyPlannedConnections(
331
+ parentType: string,
332
+ parentId: string,
333
+ fieldName: string,
334
+ alias?: string,
335
+ ): Set<string> | undefined {
336
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
337
+ return this.relations.getHasMany(key)?.plannedConnections
338
+ }
339
+
340
+ commitHasMany(
341
+ parentType: string,
342
+ parentId: string,
343
+ fieldName: string,
344
+ newServerIds: string[],
345
+ alias?: string,
346
+ ): void {
347
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
348
+ this.relations.commitHasMany(key, newServerIds)
349
+ this.notifyRelationSubscribers(key)
350
+ }
351
+
352
+ resetHasMany(
353
+ parentType: string,
354
+ parentId: string,
355
+ fieldName: string,
356
+ alias?: string,
357
+ ): void {
358
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
359
+ this.relations.resetHasMany(key)
360
+ this.notifyRelationSubscribers(key)
361
+ }
362
+
363
+ addToHasMany(
364
+ parentType: string,
365
+ parentId: string,
366
+ fieldName: string,
367
+ itemId: string,
368
+ alias?: string,
369
+ ): void {
370
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
371
+ this.relations.addToHasMany(key, itemId)
372
+ this.notifyRelationSubscribers(key)
373
+ }
374
+
375
+ removeFromHasMany(
376
+ parentType: string,
377
+ parentId: string,
378
+ fieldName: string,
379
+ itemId: string,
380
+ alias?: string,
381
+ ): void {
382
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
383
+ const result = this.relations.removeFromHasMany(key, itemId)
384
+ if (result === 'planned_removal') {
385
+ this.notifyRelationSubscribers(key)
386
+ } else if (result === 'cancelled_connection') {
387
+ this.notifyRelationSubscribers(key)
388
+ }
389
+ }
390
+
391
+ moveInHasMany(
392
+ parentType: string,
393
+ parentId: string,
394
+ fieldName: string,
395
+ fromIndex: number,
396
+ toIndex: number,
397
+ alias?: string,
398
+ ): void {
399
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
400
+ this.relations.moveInHasMany(key, fromIndex, toIndex)
401
+ this.notifyRelationSubscribers(key)
402
+ }
403
+
404
+ getHasManyOrderedIds(
405
+ parentType: string,
406
+ parentId: string,
407
+ fieldName: string,
408
+ alias?: string,
409
+ ): string[] {
410
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
411
+ return this.relations.getHasManyOrderedIds(key)
412
+ }
413
+
414
+ isHasManyItemCreated(
415
+ parentType: string,
416
+ parentId: string,
417
+ fieldName: string,
418
+ itemId: string,
419
+ alias?: string,
420
+ ): boolean {
421
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
422
+ const state = this.relations.getHasMany(key)
423
+ return state?.createdEntities.has(itemId) ?? false
424
+ }
425
+
426
+ getHasManyCreatedEntities(
427
+ parentType: string,
428
+ parentId: string,
429
+ fieldName: string,
430
+ alias?: string,
431
+ ): Set<string> | undefined {
432
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
433
+ return this.relations.getHasMany(key)?.createdEntities
434
+ }
435
+
436
+ // ==================== Persisting State (delegated to EntityMetaStore) ====================
437
+
438
+ isPersisting(entityType: string, id: string): boolean {
439
+ const key = this.getEntityKey(entityType, id)
440
+ return this.meta.isPersisting(key)
441
+ }
442
+
443
+ setPersisting(entityType: string, id: string, isPersisting: boolean): void {
444
+ const key = this.getEntityKey(entityType, id)
445
+ this.meta.setPersisting(key, isPersisting)
446
+ this.notifyEntitySubscribers(key)
447
+ }
448
+
449
+ // ==================== Error State (delegated to ErrorStore) ====================
450
+
451
+ getFieldErrors(entityType: string, id: string, fieldName: string): readonly FieldError[] {
452
+ const key = this.getRelationKey(entityType, id, fieldName)
453
+ return this.errors.getFieldErrors(key)
454
+ }
455
+
456
+ addFieldError(entityType: string, id: string, fieldName: string, error: FieldError): void {
457
+ const key = this.getRelationKey(entityType, id, fieldName)
458
+ this.errors.addFieldError(key, error)
459
+ this.notifyEntitySubscribers(this.getEntityKey(entityType, id))
460
+ }
461
+
462
+ clearFieldErrors(
463
+ entityType: string,
464
+ id: string,
465
+ fieldName: string,
466
+ source?: 'client' | 'server',
467
+ ): void {
468
+ const key = this.getRelationKey(entityType, id, fieldName)
469
+ this.errors.clearFieldErrors(key, source)
470
+ this.notifyEntitySubscribers(this.getEntityKey(entityType, id))
471
+ }
472
+
473
+ clearNonStickyFieldErrors(entityType: string, id: string, fieldName: string): void {
474
+ const key = this.getRelationKey(entityType, id, fieldName)
475
+ if (this.errors.clearNonStickyFieldErrors(key)) {
476
+ this.notifyEntitySubscribers(this.getEntityKey(entityType, id))
477
+ }
478
+ }
479
+
480
+ getEntityErrors(entityType: string, id: string): readonly FieldError[] {
481
+ const key = this.getEntityKey(entityType, id)
482
+ return this.errors.getEntityErrors(key)
483
+ }
484
+
485
+ addEntityError(entityType: string, id: string, error: FieldError): void {
486
+ const key = this.getEntityKey(entityType, id)
487
+ this.errors.addEntityError(key, error)
488
+ this.notifyEntitySubscribers(key)
489
+ }
490
+
491
+ clearEntityErrors(entityType: string, id: string, source?: 'client' | 'server'): void {
492
+ const key = this.getEntityKey(entityType, id)
493
+ this.errors.clearEntityErrors(key, source)
494
+ this.notifyEntitySubscribers(key)
495
+ }
496
+
497
+ getRelationErrors(entityType: string, id: string, relationName: string): readonly FieldError[] {
498
+ const key = this.getRelationKey(entityType, id, relationName)
499
+ return this.errors.getRelationErrors(key)
500
+ }
501
+
502
+ addRelationError(entityType: string, id: string, relationName: string, error: FieldError): void {
503
+ const key = this.getRelationKey(entityType, id, relationName)
504
+ this.errors.addRelationError(key, error)
505
+ this.notifyRelationSubscribers(key)
506
+ }
507
+
508
+ clearRelationErrors(
509
+ entityType: string,
510
+ id: string,
511
+ relationName: string,
512
+ source?: 'client' | 'server',
513
+ ): void {
514
+ const key = this.getRelationKey(entityType, id, relationName)
515
+ this.errors.clearRelationErrors(key, source)
516
+ this.notifyRelationSubscribers(key)
517
+ }
518
+
519
+ clearAllServerErrors(entityType: string, id: string): void {
520
+ const entityKey = this.getEntityKey(entityType, id)
521
+ const keyPrefix = `${entityType}:${id}:`
522
+ this.errors.clearAllServerErrors(entityKey, keyPrefix)
523
+ }
524
+
525
+ clearAllErrors(entityType: string, id: string): void {
526
+ const entityKey = this.getEntityKey(entityType, id)
527
+ const keyPrefix = `${entityType}:${id}:`
528
+ this.errors.clearAllErrors(entityKey, keyPrefix)
529
+ this.notifyEntitySubscribers(entityKey)
530
+ }
531
+
532
+ hasClientErrors(entityType: string, id: string): boolean {
533
+ const entityKey = this.getEntityKey(entityType, id)
534
+ const keyPrefix = `${entityType}:${id}:`
535
+ return this.errors.hasClientErrors(entityKey, keyPrefix)
536
+ }
537
+
538
+ hasAnyErrors(entityType: string, id: string): boolean {
539
+ const entityKey = this.getEntityKey(entityType, id)
540
+ const keyPrefix = `${entityType}:${id}:`
541
+ return this.errors.hasAnyErrors(entityKey, keyPrefix)
542
+ }
543
+
544
+ // ==================== Touched State (delegated to TouchedStore) ====================
545
+
546
+ isFieldTouched(entityType: string, id: string, fieldName: string): boolean {
547
+ const key = this.getRelationKey(entityType, id, fieldName)
548
+ return this.touched.isFieldTouched(key)
549
+ }
550
+
551
+ setFieldTouched(entityType: string, id: string, fieldName: string, touched: boolean): void {
552
+ const key = this.getRelationKey(entityType, id, fieldName)
553
+ if (this.touched.setFieldTouched(key, touched)) {
554
+ this.notifyEntitySubscribers(this.getEntityKey(entityType, id))
555
+ }
556
+ }
557
+
558
+ clearEntityTouchedState(entityType: string, id: string): void {
559
+ const keyPrefix = `${entityType}:${id}:`
560
+ this.touched.clearForEntity(keyPrefix)
561
+ this.notifyEntitySubscribers(this.getEntityKey(entityType, id))
562
+ }
563
+
564
+ // ==================== Relation State (delegated to RelationStore) ====================
565
+
566
+ getOrCreateRelation(
567
+ parentType: string,
568
+ parentId: string,
569
+ fieldName: string,
570
+ initial: Omit<StoredRelationState, 'version'>,
571
+ ): StoredRelationState {
572
+ const key = this.getRelationKey(parentType, parentId, fieldName)
573
+ return this.relations.getOrCreateRelation(key, initial)
574
+ }
575
+
576
+ getRelation(
577
+ parentType: string,
578
+ parentId: string,
579
+ fieldName: string,
580
+ ): StoredRelationState | undefined {
581
+ const key = this.getRelationKey(parentType, parentId, fieldName)
582
+ return this.relations.getRelation(key)
583
+ }
584
+
585
+ setRelation(
586
+ parentType: string,
587
+ parentId: string,
588
+ fieldName: string,
589
+ updates: Partial<Omit<StoredRelationState, 'version'>>,
590
+ ): void {
591
+ const key = this.getRelationKey(parentType, parentId, fieldName)
592
+ const entityKey = this.getEntityKey(parentType, parentId)
593
+ const entitySnapshot = this.entitySnapshots.get(entityKey)
594
+ this.relations.setRelation(key, updates, entitySnapshot, fieldName)
595
+ this.notifyRelationSubscribers(key)
596
+ }
597
+
598
+ commitRelation(parentType: string, parentId: string, fieldName: string): void {
599
+ const key = this.getRelationKey(parentType, parentId, fieldName)
600
+ this.relations.commitRelation(key)
601
+ this.notifyRelationSubscribers(key)
602
+ }
603
+
604
+ resetRelation(parentType: string, parentId: string, fieldName: string): void {
605
+ const key = this.getRelationKey(parentType, parentId, fieldName)
606
+ this.relations.resetRelation(key)
607
+ this.notifyRelationSubscribers(key)
608
+ }
609
+
610
+ commitAllRelations(entityType: string, entityId: string): void {
611
+ const keyPrefix = `${entityType}:${entityId}:`
612
+ this.relations.commitAllRelations(keyPrefix)
613
+ }
614
+
615
+ resetAllRelations(entityType: string, entityId: string): void {
616
+ const keyPrefix = `${entityType}:${entityId}:`
617
+ this.relations.resetAllRelations(keyPrefix)
618
+ }
619
+
620
+ getAllRelationsForEntity(entityType: string, entityId: string): Map<string, StoredRelationState> {
621
+ const keyPrefix = `${entityType}:${entityId}:`
622
+ return this.relations.getAllRelationsForEntity(keyPrefix)
623
+ }
624
+
625
+ getAllHasManyForEntity(entityType: string, entityId: string): Map<string, StoredHasManyState> {
626
+ const keyPrefix = `${entityType}:${entityId}:`
627
+ return this.relations.getAllHasManyForEntity(keyPrefix)
628
+ }
629
+
630
+ restoreHasManyState(
631
+ parentType: string,
632
+ parentId: string,
633
+ fieldName: string,
634
+ state: StoredHasManyState,
635
+ alias?: string,
636
+ ): void {
637
+ const key = this.getRelationKey(parentType, parentId, alias ?? fieldName)
638
+ this.relations.restoreHasManyState(key, state)
639
+ this.notifyRelationSubscribers(key)
640
+ }
641
+
642
+ // ==================== Subscriptions (delegated to SubscriptionManager) ====================
643
+
644
+ subscribeToEntity(entityType: string, id: string, callback: Subscriber): () => void {
645
+ const key = this.getEntityKey(entityType, id)
646
+ return this.subscriptions.subscribeToEntity(key, callback)
647
+ }
648
+
649
+ subscribeToRelation(
650
+ parentType: string,
651
+ parentId: string,
652
+ fieldName: string,
653
+ callback: Subscriber,
654
+ ): () => void {
655
+ const key = this.getRelationKey(parentType, parentId, fieldName)
656
+ return this.subscriptions.subscribeToRelation(key, callback)
657
+ }
658
+
659
+ subscribe(callback: Subscriber): () => void {
660
+ return this.subscriptions.subscribe(callback)
661
+ }
662
+
663
+ getVersion(): number {
664
+ return this.subscriptions.getVersion()
665
+ }
666
+
667
+ notify(): void {
668
+ this.subscriptions.notify()
669
+ }
670
+
671
+ // ==================== Parent-Child Relationships ====================
672
+
673
+ registerParentChild(parentType: string, parentId: string, childType: string, childId: string): void {
674
+ const parentKey = this.getEntityKey(parentType, parentId)
675
+ const childKey = this.getEntityKey(childType, childId)
676
+ this.subscriptions.registerParentChild(parentKey, childKey)
677
+ }
678
+
679
+ unregisterParentChild(parentType: string, parentId: string, childType: string, childId: string): void {
680
+ const parentKey = this.getEntityKey(parentType, parentId)
681
+ const childKey = this.getEntityKey(childType, childId)
682
+ this.subscriptions.unregisterParentChild(parentKey, childKey)
683
+ }
684
+
685
+ // ==================== Partial Snapshot Export/Import ====================
686
+
687
+ exportPartialSnapshot(keys: {
688
+ entityKeys: string[]
689
+ relationKeys: string[]
690
+ hasManyKeys: string[]
691
+ }): {
692
+ entitySnapshots: Map<string, EntitySnapshot>
693
+ relationStates: Map<string, StoredRelationState>
694
+ hasManyStates: Map<string, StoredHasManyState>
695
+ entityMetas: Map<string, EntityMeta>
696
+ } {
697
+ return {
698
+ entitySnapshots: this.entitySnapshots.exportSnapshots(keys.entityKeys),
699
+ relationStates: this.relations.exportRelationStates(keys.relationKeys),
700
+ hasManyStates: this.relations.exportHasManyStates(keys.hasManyKeys),
701
+ entityMetas: this.meta.exportMetas(keys.entityKeys),
702
+ }
703
+ }
704
+
705
+ importPartialSnapshot(snapshot: {
706
+ entitySnapshots: Map<string, EntitySnapshot>
707
+ relationStates: Map<string, StoredRelationState>
708
+ hasManyStates: Map<string, StoredHasManyState>
709
+ entityMetas: Map<string, EntityMeta>
710
+ }): void {
711
+ const notifiedEntityKeys = this.entitySnapshots.importSnapshots(snapshot.entitySnapshots)
712
+
713
+ this.meta.importMetas(snapshot.entityMetas)
714
+
715
+ const relationKeys = this.relations.importRelationStates(snapshot.relationStates)
716
+ const hasManyKeys = this.relations.importHasManyStates(snapshot.hasManyStates)
717
+ const notifiedRelationKeys = new Set([...relationKeys, ...hasManyKeys])
718
+
719
+ this.subscriptions.notifyGlobal()
720
+
721
+ for (const key of notifiedEntityKeys) {
722
+ this.subscriptions.notifyEntityDirect(key)
723
+ }
724
+
725
+ for (const key of notifiedRelationKeys) {
726
+ this.subscriptions.notifyRelationDirect(key)
727
+ }
728
+ }
729
+
730
+ // ==================== Dirty Tracking (delegated to DirtyTracker) ====================
731
+
732
+ getAllDirtyEntities(): Array<{
733
+ entityType: string
734
+ entityId: string
735
+ changeType: 'create' | 'update' | 'delete'
736
+ }> {
737
+ return this.dirtyTracker.getAllDirtyEntities()
738
+ }
739
+
740
+ getDirtyFields(entityType: string, entityId: string): string[] {
741
+ return this.dirtyTracker.getDirtyFields(entityType, entityId)
742
+ }
743
+
744
+ getDirtyRelations(entityType: string, entityId: string): string[] {
745
+ return this.dirtyTracker.getDirtyRelations(entityType, entityId)
746
+ }
747
+
748
+ commitFields(entityType: string, entityId: string, fieldNames: string[]): void {
749
+ const key = this.getEntityKey(entityType, entityId)
750
+ this.entitySnapshots.commitFields(key, fieldNames)
751
+ this.notifyEntitySubscribers(key)
752
+ }
753
+
754
+ // ==================== Utility ====================
755
+
756
+ clear(): void {
757
+ this.entitySnapshots.clear()
758
+ this.meta.clear()
759
+ this.relations.clear()
760
+ this.errors.clear()
761
+ this.touched.clear()
762
+
763
+ this.subscriptions.notify()
764
+ }
765
+ }