@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,321 @@
1
+ import type { ActionDispatcher } from '../core/ActionDispatcher.js'
2
+ import type { SnapshotStore } from '../store/SnapshotStore.js'
3
+ import { generatePlaceholderId } from '../store/SnapshotStore.js'
4
+ import {
5
+ FIELD_REF_META,
6
+ type EntityRef,
7
+ type SelectedEntityFields,
8
+ type FieldRefMeta,
9
+ type Unsubscribe,
10
+ } from './types.js'
11
+ import type { ErrorInput, FieldError } from '../errors/types.js'
12
+ import type {
13
+ EventTypeMap,
14
+ AfterEventTypes,
15
+ BeforeEventTypes,
16
+ EventListener,
17
+ Interceptor,
18
+ EntityPersistedEvent,
19
+ EntityPersistingEvent,
20
+ } from '../events/types.js'
21
+ import { createAliasProxy } from './proxyFactory.js'
22
+
23
+ /**
24
+ * PlaceholderHandle provides access to a placeholder entity (for creating new entities).
25
+ * Implements EntityRef interface with a placeholder ID.
26
+ * Reads/writes from placeholderData in the relation state.
27
+ *
28
+ * @typeParam TEntity - The full entity type
29
+ * @typeParam TSelected - The selected subset of fields
30
+ */
31
+ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEntity>
32
+ implements EntityRef<TEntity, TSelected>
33
+ {
34
+ /** Runtime brand symbols for validation */
35
+ readonly __brands?: Set<symbol>
36
+
37
+ /** Type brand for schema - placeholder at runtime */
38
+ declare readonly __schema: Record<string, object>
39
+
40
+ /** Placeholder ID for this handle */
41
+ private readonly placeholderId: string
42
+
43
+ // $ aliases - handled by proxy at runtime, declared for TypeScript
44
+ declare readonly $fields: SelectedEntityFields<TEntity, TSelected>
45
+ declare readonly $data: TSelected | null
46
+ declare readonly $isDirty: boolean
47
+ declare readonly $persistedId: null
48
+ declare readonly $isNew: boolean
49
+ declare readonly $errors: readonly FieldError[]
50
+ declare readonly $hasError: boolean
51
+ declare $addError: (error: ErrorInput) => void
52
+ declare $clearErrors: () => void
53
+ declare $clearAllErrors: () => void
54
+ declare $on: <E extends AfterEventTypes>(eventType: E, listener: EventListener<EventTypeMap[E]>) => Unsubscribe
55
+ declare $intercept: <E extends BeforeEventTypes>(eventType: E, interceptor: Interceptor<EventTypeMap[E]>) => Unsubscribe
56
+ declare $onPersisted: (listener: EventListener<EntityPersistedEvent>) => Unsubscribe
57
+ declare $interceptPersisting: (interceptor: Interceptor<EntityPersistingEvent>) => Unsubscribe
58
+
59
+ private constructor(
60
+ private readonly parentEntityType: string,
61
+ private readonly parentEntityId: string,
62
+ private readonly fieldName: string,
63
+ private readonly targetType: string,
64
+ private readonly store: SnapshotStore,
65
+ private readonly dispatcher: ActionDispatcher,
66
+ brands?: Set<symbol>,
67
+ ) {
68
+ this.__brands = brands
69
+ this.placeholderId = generatePlaceholderId()
70
+ }
71
+
72
+ static create<TEntity extends object = object, TSelected = TEntity>(
73
+ parentEntityType: string,
74
+ parentEntityId: string,
75
+ fieldName: string,
76
+ targetType: string,
77
+ store: SnapshotStore,
78
+ dispatcher: ActionDispatcher,
79
+ brands?: Set<symbol>,
80
+ ): PlaceholderHandle<TEntity, TSelected> {
81
+ return createAliasProxy(new PlaceholderHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, targetType, store, dispatcher, brands))
82
+ }
83
+
84
+ /**
85
+ * Gets the placeholder ID.
86
+ */
87
+ get id(): string {
88
+ return this.placeholderId
89
+ }
90
+
91
+ /**
92
+ * Gets placeholder data from the relation state.
93
+ */
94
+ get data(): TSelected | null {
95
+ const relation = this.store.getRelation(
96
+ this.parentEntityType,
97
+ this.parentEntityId,
98
+ this.fieldName,
99
+ )
100
+ if (!relation || Object.keys(relation.placeholderData).length === 0) {
101
+ return null
102
+ }
103
+ return relation.placeholderData as TSelected
104
+ }
105
+
106
+ /**
107
+ * Placeholder is dirty if it has any data.
108
+ */
109
+ get isDirty(): boolean {
110
+ const relation = this.store.getRelation(
111
+ this.parentEntityType,
112
+ this.parentEntityId,
113
+ this.fieldName,
114
+ )
115
+ return relation ? Object.keys(relation.placeholderData).length > 0 : false
116
+ }
117
+
118
+ /**
119
+ * Placeholder entities are always new (not yet persisted).
120
+ */
121
+ get persistedId(): null {
122
+ return null
123
+ }
124
+
125
+ /**
126
+ * Placeholder entities are always new.
127
+ */
128
+ get isNew(): boolean {
129
+ return true
130
+ }
131
+
132
+ /**
133
+ * Gets field accessors that read/write placeholder data.
134
+ */
135
+ get fields(): SelectedEntityFields<TEntity, TSelected> {
136
+ return new Proxy({} as SelectedEntityFields<TEntity, TSelected>, {
137
+ get: (_, fieldName: string) => {
138
+ return this.createPlaceholderFieldHandle(fieldName)
139
+ },
140
+ })
141
+ }
142
+
143
+ /**
144
+ * Creates a field handle for placeholder data.
145
+ */
146
+ private createPlaceholderFieldHandle(fieldName: string): unknown {
147
+ const self = this
148
+
149
+ return {
150
+ get [FIELD_REF_META](): FieldRefMeta {
151
+ return {
152
+ entityType: self.targetType,
153
+ entityId: self.placeholderId,
154
+ path: [fieldName],
155
+ fieldName,
156
+ isArray: false,
157
+ isRelation: false,
158
+ }
159
+ },
160
+ get value(): unknown {
161
+ const relation = self.store.getRelation(
162
+ self.parentEntityType,
163
+ self.parentEntityId,
164
+ self.fieldName,
165
+ )
166
+ return relation?.placeholderData[fieldName] ?? null
167
+ },
168
+ get serverValue(): unknown {
169
+ return null
170
+ },
171
+ get isDirty(): boolean {
172
+ const relation = self.store.getRelation(
173
+ self.parentEntityType,
174
+ self.parentEntityId,
175
+ self.fieldName,
176
+ )
177
+ return fieldName in (relation?.placeholderData ?? {})
178
+ },
179
+ setValue: (value: unknown): void => {
180
+ self.dispatcher.dispatch({
181
+ type: 'SET_PLACEHOLDER_DATA',
182
+ entityType: self.parentEntityType,
183
+ entityId: self.parentEntityId,
184
+ fieldName: self.fieldName,
185
+ fieldPath: [fieldName],
186
+ value,
187
+ })
188
+ },
189
+ get inputProps() {
190
+ const getValue = () => {
191
+ const relation = self.store.getRelation(
192
+ self.parentEntityType,
193
+ self.parentEntityId,
194
+ self.fieldName,
195
+ )
196
+ return relation?.placeholderData[fieldName] ?? null
197
+ }
198
+ const setValue = (value: unknown) => {
199
+ self.dispatcher.dispatch({
200
+ type: 'SET_PLACEHOLDER_DATA',
201
+ entityType: self.parentEntityType,
202
+ entityId: self.parentEntityId,
203
+ fieldName: self.fieldName,
204
+ fieldPath: [fieldName],
205
+ value,
206
+ })
207
+ }
208
+ return {
209
+ get value() {
210
+ return getValue()
211
+ },
212
+ setValue,
213
+ onChange: setValue,
214
+ }
215
+ },
216
+ path: [fieldName],
217
+ fieldName,
218
+ // Error properties for FieldRef interface
219
+ get errors(): readonly FieldError[] {
220
+ return []
221
+ },
222
+ get hasError(): boolean {
223
+ return false
224
+ },
225
+ addError(_error: ErrorInput): void {
226
+ // Placeholder fields don't store errors
227
+ },
228
+ clearErrors(): void {
229
+ // Placeholder fields don't have errors to clear
230
+ },
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Type brand for EntityRef compatibility.
236
+ */
237
+ get __entityType(): TEntity {
238
+ return undefined as unknown as TEntity
239
+ }
240
+
241
+ /**
242
+ * Entity name for type inference.
243
+ */
244
+ get __entityName(): string {
245
+ return this.targetType
246
+ }
247
+
248
+ /**
249
+ * Placeholder entities don't have persistent errors.
250
+ * Returns empty array.
251
+ */
252
+ get errors(): readonly FieldError[] {
253
+ return []
254
+ }
255
+
256
+ /**
257
+ * Placeholder entities don't have errors.
258
+ */
259
+ get hasError(): boolean {
260
+ return false
261
+ }
262
+
263
+ /**
264
+ * No-op for placeholder entities.
265
+ */
266
+ addError(_error: ErrorInput): void {
267
+ // Placeholder entities don't store errors
268
+ }
269
+
270
+ /**
271
+ * No-op for placeholder entities.
272
+ */
273
+ clearErrors(): void {
274
+ // Placeholder entities don't have errors to clear
275
+ }
276
+
277
+ /**
278
+ * No-op for placeholder entities.
279
+ */
280
+ clearAllErrors(): void {
281
+ // Placeholder entities don't have errors to clear
282
+ }
283
+
284
+ // ==================== Event Subscriptions ====================
285
+ // Placeholder entities don't fire events - these are no-ops that return dummy unsubscribe functions
286
+
287
+ /**
288
+ * No-op for placeholder entities.
289
+ */
290
+ on<E extends AfterEventTypes>(
291
+ _eventType: E,
292
+ _listener: EventListener<EventTypeMap[E]>,
293
+ ): Unsubscribe {
294
+ return () => {}
295
+ }
296
+
297
+ /**
298
+ * No-op for placeholder entities.
299
+ */
300
+ intercept<E extends BeforeEventTypes>(
301
+ _eventType: E,
302
+ _interceptor: Interceptor<EventTypeMap[E]>,
303
+ ): Unsubscribe {
304
+ return () => {}
305
+ }
306
+
307
+ /**
308
+ * No-op for placeholder entities.
309
+ */
310
+ onPersisted(_listener: EventListener<EntityPersistedEvent>): Unsubscribe {
311
+ return () => {}
312
+ }
313
+
314
+ /**
315
+ * No-op for placeholder entities.
316
+ */
317
+ interceptPersisting(_interceptor: Interceptor<EntityPersistingEvent>): Unsubscribe {
318
+ return () => {}
319
+ }
320
+
321
+ }
@@ -0,0 +1,37 @@
1
+ export { BaseHandle, EntityRelatedHandle } from './BaseHandle.js'
2
+ export { FieldHandle } from './FieldHandle.js'
3
+ export { EntityHandle } from './EntityHandle.js'
4
+ export { HasOneHandle } from './HasOneHandle.js'
5
+ export { PlaceholderHandle } from './PlaceholderHandle.js'
6
+ export { HasManyListHandle } from './HasManyListHandle.js'
7
+ export {
8
+ FIELD_REF_META,
9
+ type HasOneRelationState,
10
+ type EntityFields,
11
+ type SelectedEntityFields,
12
+ type SelectedEntityFieldsBase,
13
+ type ScalarKeys,
14
+ type HasManyKeys,
15
+ type HasOneKeys,
16
+ type FieldRefMeta,
17
+ type InputProps,
18
+ // Full types (for explicit selection - have .value, .length, etc.)
19
+ type FieldRef,
20
+ type HasManyRef,
21
+ type HasOneRef,
22
+ type HasOneAccessor,
23
+ type EntityRef,
24
+ type EntityAccessor,
25
+ // Base types (for implicit selection - no .value, .length, etc.)
26
+ type FieldRefBase,
27
+ type HasManyRefBase,
28
+ type HasOneRefBase,
29
+ type HasOneAccessorBase,
30
+ type EntityRefBase,
31
+ type EntityAccessorBase,
32
+ type Unsubscribe,
33
+ // Type extraction helpers
34
+ type ExtractHasOneEntityName,
35
+ type ExtractHasManyEntityName,
36
+ type ExtractRoleMap,
37
+ } from './types.js'
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Shared proxy factory for creating handle proxies with direct field access and $ aliasing.
3
+ *
4
+ * This module provides:
5
+ * 1. createHandleProxy - for EntityHandle/HasOneHandle with field access via proxy
6
+ * 2. createAliasProxy - for FieldHandle/HasManyListHandle/PlaceholderHandle with $ aliasing only
7
+ *
8
+ * The $ prefix convention allows handle properties to avoid collision with entity field names:
9
+ * - `entity.title` → field access (returns FieldHandle)
10
+ * - `entity.$isDirty` → handle property
11
+ */
12
+
13
+ import { FIELD_REF_META } from './types.js'
14
+
15
+ /**
16
+ * Shared base properties for entity-like handles (EntityHandle, HasOneHandle).
17
+ * Extracting a common set prevents bugs from inconsistent maintenance (e.g. missing `id`).
18
+ */
19
+ const BASE_ENTITY_PROPERTIES: (string | symbol)[] = [
20
+ // Entity identity — id is always the entity ID, not a field handle
21
+ 'id',
22
+ // Type brands (phantom types)
23
+ '__entityType', '__entityName', '__brands', '__schema',
24
+ // Symbol
25
+ FIELD_REF_META,
26
+ // Core internals shared by all entity-like handles
27
+ 'entityType', 'entityId', 'store', 'dispatcher', 'schema',
28
+ 'getEntityData', 'getServerData', 'assertNotDisposed', 'isDisposed',
29
+ 'dispose', 'subscribe',
30
+ ]
31
+
32
+ /**
33
+ * Known properties on EntityHandle that should NOT be treated as field access.
34
+ * Properties like `fields`, `data`, `errors` are NOT included - use $ prefix for those.
35
+ */
36
+ export const ENTITY_HANDLE_PROPERTIES = new Set<string | symbol>([
37
+ ...BASE_ENTITY_PROPERTIES,
38
+ // Entity-specific internals
39
+ 'fieldHandleCache', 'relationHandleCache',
40
+ // Internal methods (unlikely to be field names)
41
+ 'field', 'hasOne', 'hasMany', 'getSnapshot',
42
+ 'reset', 'commit',
43
+ 'getDirtyFields', 'getDirtyRelations',
44
+ // State properties (unlikely to be field names)
45
+ 'serverData', 'isLoaded', 'isLoading', 'isError', 'error', 'isPersisting',
46
+ ])
47
+
48
+ /**
49
+ * Known properties on HasOneHandle that should NOT be treated as field access.
50
+ * Properties like `fields`, `entity`, `errors`, `state` are NOT included - they require $ prefix.
51
+ */
52
+ export const HAS_ONE_HANDLE_PROPERTIES = new Set<string | symbol>([
53
+ ...BASE_ENTITY_PROPERTIES,
54
+ // HasOne-specific internals
55
+ 'fieldName', 'targetType',
56
+ 'entityHandleCache', 'placeholderCache', 'ensureRelatedEntitySnapshot', 'relatedId',
57
+ // Methods that are unlikely to be field names
58
+ 'connect', 'disconnect', 'delete', 'reset',
59
+ ])
60
+
61
+ /**
62
+ * All properties on FieldHandle (no field access proxy needed, just $ aliasing).
63
+ */
64
+ export const FIELD_HANDLE_PROPERTIES = new Set<string | symbol>([
65
+ FIELD_REF_META,
66
+ // Core properties
67
+ 'value', 'serverValue', 'isDirty', 'inputProps', 'errors', 'hasError',
68
+ 'path', 'fieldName',
69
+ // Methods
70
+ 'setValue', 'addError', 'clearErrors', 'onChange', 'onChanging', 'nested',
71
+ // Internal
72
+ 'entityType', 'entityId', 'fieldPath', 'store', 'dispatcher',
73
+ 'getEntityData', 'getServerData', 'assertNotDisposed', 'isDisposed',
74
+ 'subscribe', 'dispose',
75
+ ])
76
+
77
+ /**
78
+ * All properties on HasManyListHandle (no field access proxy needed, just $ aliasing).
79
+ */
80
+ export const HAS_MANY_HANDLE_PROPERTIES = new Set<string | symbol>([
81
+ FIELD_REF_META,
82
+ // Type brands
83
+ '__entityType', '__brands',
84
+ // Core properties
85
+ 'items', 'length', 'isDirty', 'errors', 'hasError',
86
+ // Methods
87
+ 'map', 'add', 'remove', 'move', 'connect', 'disconnect', 'reset',
88
+ 'getItemHandle', 'addError', 'clearErrors',
89
+ 'onItemConnected', 'onItemDisconnected', 'interceptItemConnecting', 'interceptItemDisconnecting',
90
+ // Internal
91
+ 'entityType', 'entityId', 'fieldName', 'itemType', 'store', 'dispatcher', 'schema',
92
+ 'itemHandleCache', 'getEntityData', 'getServerData', 'assertNotDisposed', 'isDisposed',
93
+ 'subscribe', 'dispose', 'ensureItemSnapshots',
94
+ ])
95
+
96
+ /**
97
+ * All properties on PlaceholderHandle (no field access proxy needed, just $ aliasing).
98
+ */
99
+ export const PLACEHOLDER_HANDLE_PROPERTIES = new Set<string | symbol>([
100
+ // Core properties
101
+ 'id', 'data', 'isDirty', 'persistedId', 'isNew', 'fields', 'errors', 'hasError',
102
+ // Type brands
103
+ '__entityType', '__entityName', '__brands',
104
+ // Methods
105
+ 'addError', 'clearErrors', 'clearAllErrors',
106
+ 'on', 'intercept', 'onPersisted', 'interceptPersisting',
107
+ // Internal
108
+ 'parentEntityType', 'parentEntityId', 'fieldName', 'targetType', 'store', 'dispatcher',
109
+ 'placeholderId', 'createPlaceholderFieldHandle',
110
+ ])
111
+
112
+ /**
113
+ * Configuration for creating a handle proxy.
114
+ */
115
+ export interface HandleProxyConfig<T extends object> {
116
+ /** Set of property names that should pass through to the handle */
117
+ knownProperties: Set<string | symbol>
118
+ /** Function to get the fields proxy from the handle */
119
+ getFields: (target: T) => object
120
+ }
121
+
122
+ /**
123
+ * Creates a proxy around a handle that supports direct field access.
124
+ *
125
+ * - `handle.fieldName` is equivalent to `handle.$fields.fieldName`
126
+ * - `handle.$propertyName` accesses handle properties with $ prefix stripped
127
+ * - Known properties pass through directly to the handle
128
+ *
129
+ * @param handle - The handle instance to wrap
130
+ * @param config - Configuration for the proxy
131
+ * @returns Proxied handle with direct field access support
132
+ */
133
+ export function createHandleProxy<T extends object>(
134
+ handle: T,
135
+ config: HandleProxyConfig<T>,
136
+ ): T {
137
+ return new Proxy(handle, {
138
+ get(target, prop, _receiver) {
139
+ // Symbols and non-string props - pass through
140
+ if (typeof prop !== 'string') {
141
+ return Reflect.get(target, prop, target)
142
+ }
143
+
144
+ // $ prefixed - strip $ and access handle property
145
+ if (prop.startsWith('$')) {
146
+ const realProp = prop.slice(1)
147
+ // Use target as receiver so getters use target as `this`, not the Proxy
148
+ const value = Reflect.get(target, realProp, target)
149
+ // Bind methods to preserve `this`
150
+ if (typeof value === 'function') {
151
+ return value.bind(target)
152
+ }
153
+ return value
154
+ }
155
+
156
+ // Known handle properties - pass through for backwards compatibility
157
+ if (config.knownProperties.has(prop)) {
158
+ const value = Reflect.get(target, prop, target)
159
+ if (typeof value === 'function') {
160
+ return value.bind(target)
161
+ }
162
+ return value
163
+ }
164
+
165
+ // Otherwise, treat as field access
166
+ const fields = config.getFields(target)
167
+ return (fields as Record<string, unknown>)[prop]
168
+ },
169
+
170
+ has(target, prop) {
171
+ if (typeof prop === 'string' && !config.knownProperties.has(prop) && !prop.startsWith('$')) {
172
+ // Check if it's a field
173
+ const fields = config.getFields(target)
174
+ return prop in fields
175
+ }
176
+ return Reflect.has(target, prop)
177
+ },
178
+ })
179
+ }
180
+
181
+ /**
182
+ * Creates a simple proxy that only handles $ prefix aliasing.
183
+ * Used for handles that don't need field access proxying (FieldHandle, HasManyListHandle, PlaceholderHandle).
184
+ *
185
+ * - `handle.$propertyName` accesses `handle.propertyName`
186
+ * - All other accesses pass through to the handle
187
+ *
188
+ * @param handle - The handle instance to wrap
189
+ * @returns Proxied handle with $ alias support
190
+ */
191
+ export function createAliasProxy<T extends object>(handle: T): T {
192
+ return new Proxy(handle, {
193
+ get(target, prop, _receiver) {
194
+ // Symbols and non-string props - pass through
195
+ if (typeof prop !== 'string') {
196
+ return Reflect.get(target, prop, target)
197
+ }
198
+
199
+ // $ prefixed - strip $ and access handle property
200
+ if (prop.startsWith('$')) {
201
+ const realProp = prop.slice(1)
202
+ const value = Reflect.get(target, realProp, target)
203
+ // Bind methods to preserve `this`
204
+ if (typeof value === 'function') {
205
+ return value.bind(target)
206
+ }
207
+ return value
208
+ }
209
+
210
+ // Pass through to handle
211
+ const value = Reflect.get(target, prop, target)
212
+ if (typeof value === 'function') {
213
+ return value.bind(target)
214
+ }
215
+ return value
216
+ },
217
+ })
218
+ }