@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,743 @@
1
+ /**
2
+ * Unified type definitions for the Handle system.
3
+ *
4
+ * This module provides type-safe field accessor mapping that correctly
5
+ * distinguishes between scalar fields, hasOne relations, and hasMany relations.
6
+ *
7
+ * Type hierarchy uses inheritance for implicit/explicit selection modes:
8
+ * - Base types (e.g., FieldRefBase) - work everywhere, no direct value access
9
+ * - Full types (e.g., FieldRef) extend Base - add .value, .length, etc.
10
+ *
11
+ * Components accept Base types, so they work with both implicit and explicit selection.
12
+ */
13
+
14
+ import type { FieldHandle } from './FieldHandle.js'
15
+
16
+ // ============================================================================
17
+ // Relation State Types
18
+ // ============================================================================
19
+
20
+ /**
21
+ * State of a has-one relation.
22
+ */
23
+ export type HasOneRelationState =
24
+ | 'connected' // Points to an existing entity
25
+ | 'disconnected' // Explicitly set to null
26
+ | 'deleted' // Related entity marked for deletion
27
+ | 'creating' // Placeholder entity being filled for implicit create
28
+
29
+ import type { ComponentBrand, AnyBrand } from '../brand/ComponentBrand.js'
30
+ import type { FieldError, ErrorInput } from '../errors/types.js'
31
+ import type {
32
+ FieldChangedEvent,
33
+ FieldChangingEvent,
34
+ RelationConnectedEvent,
35
+ RelationDisconnectedEvent,
36
+ RelationConnectingEvent,
37
+ RelationDisconnectingEvent,
38
+ HasManyConnectedEvent,
39
+ HasManyDisconnectedEvent,
40
+ HasManyConnectingEvent,
41
+ HasManyDisconnectingEvent,
42
+ EntityPersistedEvent,
43
+ EntityPersistingEvent,
44
+ EventTypeMap,
45
+ AfterEventTypes,
46
+ BeforeEventTypes,
47
+ EventListener,
48
+ Interceptor,
49
+ Unsubscribe as UnsubscribeType,
50
+ } from '../events/types.js'
51
+
52
+ // Re-export Unsubscribe for convenience
53
+ export type { UnsubscribeType as Unsubscribe }
54
+
55
+ // ============================================================================
56
+ // Field Type Detection Utilities
57
+ // ============================================================================
58
+
59
+ /**
60
+ * Extracts scalar field keys from a type.
61
+ * A field is scalar if it's not an array and not an object (except 'id' which is always scalar).
62
+ */
63
+ export type ScalarKeys<T> = {
64
+ [K in keyof T]: T[K] extends (infer _U)[]
65
+ ? never
66
+ : NonNullable<T[K]> extends object
67
+ ? K extends 'id'
68
+ ? K
69
+ : never
70
+ : K
71
+ }[keyof T]
72
+
73
+ /**
74
+ * Extracts has-many relation keys from a type.
75
+ * A field is has-many if it's an array of objects.
76
+ */
77
+ export type HasManyKeys<T> = {
78
+ [K in keyof T]: T[K] extends (infer U)[]
79
+ ? U extends object
80
+ ? K
81
+ : never
82
+ : never
83
+ }[keyof T]
84
+
85
+ /**
86
+ * Extracts has-one relation keys from a type.
87
+ * A field is has-one if it's an object (but not 'id' and not an array).
88
+ */
89
+ export type HasOneKeys<T> = {
90
+ [K in keyof T]: T[K] extends (infer _U)[]
91
+ ? never
92
+ : NonNullable<T[K]> extends object
93
+ ? K extends 'id'
94
+ ? never
95
+ : K
96
+ : never
97
+ }[keyof T]
98
+
99
+ // ============================================================================
100
+ // Symbols
101
+ // ============================================================================
102
+
103
+ /**
104
+ * Marker symbol for field reference metadata
105
+ */
106
+ export const FIELD_REF_META = Symbol('FIELD_REF_META')
107
+
108
+ // ============================================================================
109
+ // Helper Types
110
+ // ============================================================================
111
+
112
+ /**
113
+ * Base metadata interface for all field references.
114
+ * Used by JSX components for selection collection.
115
+ */
116
+ export interface FieldRefMeta<TEntityName extends string = string> {
117
+ readonly entityType: TEntityName
118
+ readonly entityId: string
119
+ readonly path: string[]
120
+ readonly fieldName: string
121
+ readonly isArray: boolean
122
+ readonly isRelation: boolean
123
+ /** For enum fields: the enum type name (e.g. 'DeploymentStatus') */
124
+ readonly enumName?: string
125
+ /** For scalar fields: the column type (e.g. 'String', 'Integer', 'Date') */
126
+ readonly columnType?: string
127
+ /** For relation fields: the target entity type name */
128
+ readonly targetType?: string
129
+ }
130
+
131
+ /**
132
+ * Input props interface for form binding.
133
+ */
134
+ export interface InputProps<T> {
135
+ readonly value: T | null
136
+ readonly setValue: (value: T | null) => void
137
+ readonly onChange: (value: T | null) => void
138
+ }
139
+
140
+ /**
141
+ * Extracts the nested selection type for a relation field.
142
+ */
143
+ type ExtractNestedSelection<TSelected, K extends PropertyKey> =
144
+ K extends keyof TSelected ? TSelected[K] : never
145
+
146
+ /**
147
+ * Reverse lookup: finds entity name (schema key) from entity type.
148
+ */
149
+ export type EntityNameFromType<TSchema extends Record<string, object>, TEntityType> = {
150
+ [K in keyof TSchema]: TSchema[K] extends TEntityType ? K : never
151
+ }[keyof TSchema] & string
152
+
153
+ // ============================================================================
154
+ // SCALAR FIELD TYPES
155
+ // ============================================================================
156
+
157
+ /**
158
+ * Base interface for scalar field references.
159
+ * Works everywhere, can be passed to components.
160
+ * Does NOT include .value, .serverValue, .isDirty - use FieldRef for those.
161
+ *
162
+ * @example
163
+ * ```tsx
164
+ * // In implicit mode, task.title is FieldRefBase<string>
165
+ * // Can be passed to <Field>:
166
+ * <Field field={task.title} />
167
+ *
168
+ * // Can use mutations:
169
+ * task.title.setValue('new value')
170
+ *
171
+ * // Cannot read value directly (use FieldRef for that):
172
+ * // task.title.value // ❌ type error
173
+ * ```
174
+ */
175
+ export interface FieldRefBase<T> {
176
+ /** Internal metadata for collection phase */
177
+ readonly [FIELD_REF_META]: FieldRefMeta
178
+
179
+ /** Whether field has been touched (interacted with by user) */
180
+ readonly isTouched: boolean
181
+
182
+ /** Mark the field as touched */
183
+ touch(): void
184
+
185
+ /** Update the value */
186
+ setValue(value: T | null): void
187
+
188
+ /** Input binding props */
189
+ readonly inputProps: InputProps<T>
190
+
191
+ /** List of errors on this field */
192
+ readonly errors: readonly FieldError[]
193
+
194
+ /** Whether this field has any errors */
195
+ readonly hasError: boolean
196
+
197
+ /** Add a client-side error to this field */
198
+ addError(error: ErrorInput): void
199
+
200
+ /** Clear all errors from this field */
201
+ clearErrors(): void
202
+
203
+ /** Subscribe to field value changes */
204
+ onChange(listener: EventListener<FieldChangedEvent>): UnsubscribeType
205
+
206
+ /** Intercept field value changes (can cancel or modify) */
207
+ onChanging(interceptor: Interceptor<FieldChangingEvent>): UnsubscribeType
208
+ }
209
+
210
+ /**
211
+ * Full interface for scalar field references.
212
+ * Extends FieldRefBase with value access properties.
213
+ *
214
+ * @example
215
+ * ```tsx
216
+ * // In explicit mode, task.title is FieldRef<string>
217
+ * // Full access including value:
218
+ * const title = task.title.value
219
+ * const isDirty = task.title.isDirty
220
+ * ```
221
+ */
222
+ export interface FieldRef<T> extends FieldRefBase<T> {
223
+ /** Current value (null in collection phase, real value in runtime) */
224
+ readonly value: T | null
225
+
226
+ /** Server value for dirty tracking */
227
+ readonly serverValue: T | null
228
+
229
+ /** Whether value differs from server */
230
+ readonly isDirty: boolean
231
+ }
232
+
233
+ // ============================================================================
234
+ // HAS-MANY RELATION TYPES
235
+ // ============================================================================
236
+
237
+ /**
238
+ * Base interface for has-many relation references.
239
+ * Works everywhere, can be passed to components.
240
+ * Does NOT include .length, .items, .map - use HasManyRef for those.
241
+ *
242
+ * @example
243
+ * ```tsx
244
+ * // In implicit mode, task.developers is HasManyRefBase
245
+ * // Can be passed to <HasMany>:
246
+ * <HasMany field={task.developers}>{dev => ...}</HasMany>
247
+ *
248
+ * // Can use mutations:
249
+ * task.developers.add()
250
+ * task.developers.remove(id)
251
+ *
252
+ * // Cannot read length directly (use HasManyRef for that):
253
+ * // task.developers.length // ❌ type error
254
+ * ```
255
+ */
256
+ export interface HasManyRefBase<
257
+ TEntity,
258
+ TSelected = TEntity,
259
+ TBrand extends AnyBrand = AnyBrand,
260
+ TEntityName extends string = string,
261
+ TSchema extends Record<string, object> = Record<string, object>,
262
+ > {
263
+ /** Internal metadata for collection phase */
264
+ readonly [FIELD_REF_META]: FieldRefMeta<TEntityName>
265
+
266
+ /** Whether any item has been modified */
267
+ readonly isDirty: boolean
268
+
269
+ /** Add a new item - returns the new entity's ID (temp ID) */
270
+ add(data?: Partial<TEntity>): string
271
+
272
+ /** Remove item by ID */
273
+ remove(itemId: string): void
274
+
275
+ /** Move item from one position to another */
276
+ move(fromIndex: number, toIndex: number): void
277
+
278
+ /** Connect an existing entity to this has-many relation */
279
+ connect(itemId: string): void
280
+
281
+ /** Disconnect an entity from this has-many relation */
282
+ disconnect(itemId: string | null): void
283
+
284
+ /** Reset the relation to server state */
285
+ reset(): void
286
+
287
+ /** Type brand - ensures HasManyRef<Author> is not assignable to HasManyRef<Tag> */
288
+ readonly __entityType: TEntity
289
+
290
+ /** Type brand for entity name */
291
+ readonly __entityName: TEntityName
292
+
293
+ /** Type brand for schema */
294
+ readonly __schema?: TSchema & any
295
+
296
+ /** Runtime brand symbols for validation */
297
+ readonly __brands?: Set<symbol>
298
+
299
+ /** List of errors on this relation */
300
+ readonly errors: readonly FieldError[]
301
+
302
+ /** Whether this relation has any errors */
303
+ readonly hasError: boolean
304
+
305
+ /** Add a client-side error to this relation */
306
+ addError(error: ErrorInput): void
307
+
308
+ /** Clear all errors from this relation */
309
+ clearErrors(): void
310
+
311
+ /** Subscribe to item connected events */
312
+ onItemConnected(listener: EventListener<HasManyConnectedEvent>): UnsubscribeType
313
+
314
+ /** Subscribe to item disconnected events */
315
+ onItemDisconnected(listener: EventListener<HasManyDisconnectedEvent>): UnsubscribeType
316
+
317
+ /** Intercept item connection (can cancel) */
318
+ interceptItemConnecting(interceptor: Interceptor<HasManyConnectingEvent>): UnsubscribeType
319
+
320
+ /** Intercept item disconnection (can cancel) */
321
+ interceptItemDisconnecting(interceptor: Interceptor<HasManyDisconnectingEvent>): UnsubscribeType
322
+ }
323
+
324
+ /**
325
+ * Full interface for has-many relation references.
326
+ * Extends HasManyRefBase with collection access properties.
327
+ */
328
+ export interface HasManyRef<
329
+ TEntity,
330
+ TSelected = TEntity,
331
+ TBrand extends AnyBrand = AnyBrand,
332
+ TEntityName extends string = string,
333
+ TSchema extends Record<string, object> = Record<string, object>,
334
+ > extends HasManyRefBase<TEntity, TSelected, TBrand, TEntityName, TSchema> {
335
+ /** Number of items */
336
+ readonly length: number
337
+
338
+ /** Total count from paginateRelation (undefined if not available) */
339
+ readonly totalCount: number | undefined
340
+
341
+ /** Direct access to items array */
342
+ readonly items: EntityAccessor<TEntity, TSelected, TBrand, TEntityName, TSchema>[]
343
+
344
+ /** Iterate over items */
345
+ map<R>(fn: (item: EntityAccessor<TEntity, TSelected, TBrand, TEntityName, TSchema>, index: number) => R): R[]
346
+
347
+ /** Get an item handle by entity ID */
348
+ getById(id: string): EntityAccessor<TEntity, TSelected, TBrand, TEntityName, TSchema>
349
+ }
350
+
351
+ // ============================================================================
352
+ // HAS-ONE RELATION TYPES
353
+ // ============================================================================
354
+
355
+ /**
356
+ * Base interface for has-one relation references.
357
+ * Works everywhere, can be passed to components.
358
+ * Does NOT include .$state, .$entity, .$fields, .$data - use HasOneRef for those.
359
+ * DOES include direct field access (e.g., task.project.name).
360
+ *
361
+ * @example
362
+ * ```tsx
363
+ * // In implicit mode, task.project is HasOneRefBase (with direct field access)
364
+ * // Can be passed to <HasOne>:
365
+ * <HasOne field={task.project}>{project => ...}</HasOne>
366
+ *
367
+ * // Can access nested fields (they are also restricted):
368
+ * task.project.name // FieldRefBase<string>
369
+ *
370
+ * // Can use mutations:
371
+ * task.project.$connect('project-1')
372
+ *
373
+ * // Cannot read state directly (use HasOneRef for that):
374
+ * // task.project.$state // ❌ type error
375
+ * ```
376
+ */
377
+ export interface HasOneRefBase<
378
+ TEntity,
379
+ TSelected = TEntity,
380
+ TBrand extends AnyBrand = AnyBrand,
381
+ TEntityName extends string = string,
382
+ TSchema extends Record<string, object> = Record<string, object>,
383
+ > {
384
+ /** Internal metadata for collection phase */
385
+ readonly [FIELD_REF_META]: FieldRefMeta<TEntityName>
386
+
387
+ /** Entity ID (placeholder ID if disconnected) */
388
+ readonly id: string
389
+
390
+ /** Entity ID (alias with $ prefix) */
391
+ readonly $id: string
392
+
393
+ /** Whether relation is dirty */
394
+ readonly $isDirty: boolean
395
+
396
+ /** Whether this entity is new */
397
+ readonly $isNew: boolean
398
+
399
+ /** Server-assigned ID after persistence */
400
+ readonly $persistedId: string | null
401
+
402
+ /** Type brand for entity name */
403
+ readonly __entityName: TEntityName
404
+
405
+ /** Type brand for schema */
406
+ readonly __schema?: TSchema & any
407
+
408
+ /** Type brand - ensures type safety */
409
+ readonly __entityType: TEntity
410
+
411
+ /** Runtime brand symbols for validation */
412
+ readonly __brands?: Set<symbol>
413
+
414
+ /** Connect to existing entity */
415
+ $connect(id: string): void
416
+
417
+ /** Disconnect relation */
418
+ $disconnect(): void
419
+
420
+ /** Mark relation for deletion */
421
+ $delete(): void
422
+
423
+ /** Reset the relation to server state */
424
+ $reset(): void
425
+
426
+ /** List of errors on this relation */
427
+ readonly $errors: readonly FieldError[]
428
+
429
+ /** Whether this relation has any errors */
430
+ readonly $hasError: boolean
431
+
432
+ /** Add a client-side error to this relation */
433
+ $addError(error: ErrorInput): void
434
+
435
+ /** Clear all errors from this relation */
436
+ $clearErrors(): void
437
+
438
+ /** Clear all errors (entity-level, fields, and relations) */
439
+ $clearAllErrors(): void
440
+
441
+ /** Subscribe to connection events */
442
+ $onConnect(listener: EventListener<RelationConnectedEvent>): UnsubscribeType
443
+
444
+ /** Subscribe to disconnection events */
445
+ $onDisconnect(listener: EventListener<RelationDisconnectedEvent>): UnsubscribeType
446
+
447
+ /** Intercept connection (can cancel or modify target) */
448
+ $interceptConnect(interceptor: Interceptor<RelationConnectingEvent>): UnsubscribeType
449
+
450
+ /** Intercept disconnection (can cancel) */
451
+ $interceptDisconnect(interceptor: Interceptor<RelationDisconnectingEvent>): UnsubscribeType
452
+
453
+ /** Subscribe to any event on the related entity */
454
+ $on<E extends AfterEventTypes>(
455
+ eventType: E,
456
+ listener: EventListener<EventTypeMap[E]>,
457
+ ): UnsubscribeType
458
+
459
+ /** Intercept any before event on the related entity */
460
+ $intercept<E extends BeforeEventTypes>(
461
+ eventType: E,
462
+ interceptor: Interceptor<EventTypeMap[E]>,
463
+ ): UnsubscribeType
464
+
465
+ /** Subscribe to persist success events */
466
+ $onPersisted(listener: EventListener<EntityPersistedEvent>): UnsubscribeType
467
+
468
+ /** Intercept persist (can cancel) */
469
+ $interceptPersisting(interceptor: Interceptor<EntityPersistingEvent>): UnsubscribeType
470
+ }
471
+
472
+ /**
473
+ * Full interface for has-one relation references.
474
+ * Extends HasOneRefBase with state and entity access properties.
475
+ */
476
+ export interface HasOneRef<
477
+ TEntity,
478
+ TSelected = TEntity,
479
+ TBrand extends AnyBrand = AnyBrand,
480
+ TEntityName extends string = string,
481
+ TSchema extends Record<string, object> = Record<string, object>,
482
+ > extends HasOneRefBase<TEntity, TSelected, TBrand, TEntityName, TSchema> {
483
+ /** Raw data snapshot of the related entity */
484
+ readonly $data: TSelected | null
485
+
486
+ /** Relation state */
487
+ readonly $state: HasOneRelationState
488
+
489
+ /** Nested entity fields - only selected fields are accessible */
490
+ readonly $fields: SelectedEntityFields<TEntity, TSelected, TSchema>
491
+
492
+ /** Related entity accessor with direct field access */
493
+ readonly $entity: EntityAccessor<TEntity, TSelected, TBrand, TEntityName, TSchema>
494
+ }
495
+
496
+ /**
497
+ * HasOneRefBase with direct field access via Proxy (for implicit mode).
498
+ */
499
+ export type HasOneAccessorBase<
500
+ TEntity,
501
+ TSelected = TEntity,
502
+ TBrand extends AnyBrand = AnyBrand,
503
+ TEntityName extends string = string,
504
+ TSchema extends Record<string, object> = Record<string, object>,
505
+ > = HasOneRefBase<TEntity, TSelected, TBrand, TEntityName, TSchema> &
506
+ SelectedEntityFieldsBase<TEntity, TSelected, TSchema>
507
+
508
+ /**
509
+ * HasOneRef with direct field access via Proxy (for explicit mode).
510
+ */
511
+ export type HasOneAccessor<
512
+ TEntity,
513
+ TSelected = TEntity,
514
+ TBrand extends AnyBrand = AnyBrand,
515
+ TEntityName extends string = string,
516
+ TSchema extends Record<string, object> = Record<string, object>,
517
+ > = HasOneRef<TEntity, TSelected, TBrand, TEntityName, TSchema> &
518
+ SelectedEntityFields<TEntity, TSelected, TSchema>
519
+
520
+ // ============================================================================
521
+ // ENTITY TYPES
522
+ // ============================================================================
523
+
524
+ /**
525
+ * Base interface for entity references.
526
+ * Works everywhere, can be passed to components.
527
+ * Does NOT include .$data, .$fields - use EntityRef for those.
528
+ */
529
+ export interface EntityRefBase<
530
+ TEntity,
531
+ TSelected = TEntity,
532
+ TBrand extends AnyBrand = AnyBrand,
533
+ TEntityName extends string = string,
534
+ TSchema extends Record<string, object> = Record<string, object>,
535
+ > {
536
+ /** Entity ID */
537
+ readonly id: string
538
+
539
+ /** Whether entity is dirty */
540
+ readonly $isDirty: boolean
541
+
542
+ /** Server-assigned ID after persistence */
543
+ readonly $persistedId: string | null
544
+
545
+ /** Whether this entity is new */
546
+ readonly $isNew: boolean
547
+
548
+ /** Type brand - ensures type safety */
549
+ readonly __entityType: TEntity
550
+
551
+ /** Type brand for schema */
552
+ readonly __schema?: TSchema & any
553
+
554
+ /** Type brand for entity name */
555
+ readonly __entityName: TEntityName
556
+
557
+ /** Runtime brand symbols for validation */
558
+ readonly __brands?: Set<symbol>
559
+
560
+ /** List of entity-level errors */
561
+ readonly $errors: readonly FieldError[]
562
+
563
+ /** Whether this entity has any errors */
564
+ readonly $hasError: boolean
565
+
566
+ /** Add a client-side error to this entity */
567
+ $addError(error: ErrorInput): void
568
+
569
+ /** Clear entity-level errors */
570
+ $clearErrors(): void
571
+
572
+ /** Clear all errors (entity-level, fields, and relations) */
573
+ $clearAllErrors(): void
574
+
575
+ /** Subscribe to any event on this entity */
576
+ $on<E extends AfterEventTypes>(
577
+ eventType: E,
578
+ listener: EventListener<EventTypeMap[E]>,
579
+ ): UnsubscribeType
580
+
581
+ /** Intercept any before event on this entity */
582
+ $intercept<E extends BeforeEventTypes>(
583
+ eventType: E,
584
+ interceptor: Interceptor<EventTypeMap[E]>,
585
+ ): UnsubscribeType
586
+
587
+ /** Subscribe to persist success events */
588
+ $onPersisted(listener: EventListener<EntityPersistedEvent>): UnsubscribeType
589
+
590
+ /** Intercept persist (can cancel) */
591
+ $interceptPersisting(interceptor: Interceptor<EntityPersistingEvent>): UnsubscribeType
592
+ }
593
+
594
+ /**
595
+ * Full interface for entity references.
596
+ * Extends EntityRefBase with data access properties.
597
+ */
598
+ export interface EntityRef<
599
+ TEntity,
600
+ TSelected = TEntity,
601
+ TBrand extends AnyBrand = AnyBrand,
602
+ TEntityName extends string = string,
603
+ TSchema extends Record<string, object> = Record<string, object>,
604
+ > extends EntityRefBase<TEntity, TSelected, TBrand, TEntityName, TSchema> {
605
+ /** Typed field accessors */
606
+ readonly $fields: SelectedEntityFields<TEntity, TSelected, TSchema>
607
+
608
+ /** Raw data snapshot */
609
+ readonly $data: TSelected | null
610
+ }
611
+
612
+ /**
613
+ * EntityRefBase with direct field access via Proxy (for implicit mode).
614
+ * TRoleMap carries the role map for HasRole type inference.
615
+ */
616
+ export type EntityAccessorBase<
617
+ TEntity,
618
+ TSelected = TEntity,
619
+ TBrand extends AnyBrand = AnyBrand,
620
+ TEntityName extends string = string,
621
+ TSchema extends Record<string, object> = Record<string, object>,
622
+ TRoleMap extends Record<string, object> = Record<string, object>,
623
+ > = EntityRefBase<TEntity, TSelected, TBrand, TEntityName, TSchema> &
624
+ SelectedEntityFieldsBase<TEntity, TSelected, TSchema> & {
625
+ /** @internal phantom — role map for HasRole inference */
626
+ readonly __roleMap?: TRoleMap
627
+ }
628
+
629
+ /**
630
+ * EntityRef with direct field access via Proxy (for explicit mode).
631
+ * TRoleMap carries the role map for HasRole type inference.
632
+ */
633
+ export type EntityAccessor<
634
+ TEntity,
635
+ TSelected = TEntity,
636
+ TBrand extends AnyBrand = AnyBrand,
637
+ TEntityName extends string = string,
638
+ TSchema extends Record<string, object> = Record<string, object>,
639
+ TRoleMap extends Record<string, object> = Record<string, object>,
640
+ > = EntityRef<TEntity, TSelected, TBrand, TEntityName, TSchema> &
641
+ SelectedEntityFields<TEntity, TSelected, TSchema> & {
642
+ /** @internal phantom — role map for HasRole inference */
643
+ readonly __roleMap?: TRoleMap
644
+ }
645
+
646
+ // ============================================================================
647
+ // ENTITY FIELDS MAPPING TYPES
648
+ // ============================================================================
649
+
650
+ /**
651
+ * Maps entity fields to their correct Handle types based on structural typing.
652
+ * Uses full types (FieldHandle, HasManyRef, HasOneRef) for backwards compatibility.
653
+ */
654
+ export type EntityFields<T> = {
655
+ [K in ScalarKeys<T>]: FieldHandle<T[K]>
656
+ } & {
657
+ [K in HasManyKeys<T>]: T[K] extends (infer U)[]
658
+ ? U extends object
659
+ ? HasManyRef<U>
660
+ : never
661
+ : never
662
+ } & {
663
+ [K in HasOneKeys<T>]: HasOneRef<NonNullable<T[K]>>
664
+ }
665
+
666
+ /**
667
+ * Maps entity fields to Base types (restricted, for implicit mode).
668
+ * These types can be passed to components but don't allow direct value access.
669
+ */
670
+ export type SelectedEntityFieldsBase<
671
+ TEntity,
672
+ TSelected,
673
+ TSchema extends Record<string, object> = Record<string, object>,
674
+ > = {
675
+ [K in ScalarKeys<TEntity> & keyof TSelected]: FieldRefBase<TEntity[K]>
676
+ } & {
677
+ [K in HasManyKeys<TEntity> & keyof TSelected]: TEntity[K] extends (infer U)[]
678
+ ? U extends object
679
+ ? HasManyRefBase<U, ExtractNestedSelection<TSelected, K> extends (infer S)[] ? S : U, AnyBrand, EntityNameFromType<TSchema, U>, TSchema>
680
+ : never
681
+ : never
682
+ } & {
683
+ [K in HasOneKeys<TEntity> & keyof TSelected]: HasOneAccessorBase<
684
+ NonNullable<TEntity[K]>,
685
+ ExtractNestedSelection<TSelected, K> extends object ? ExtractNestedSelection<TSelected, K> : NonNullable<TEntity[K]>,
686
+ AnyBrand,
687
+ EntityNameFromType<TSchema, NonNullable<TEntity[K]>>,
688
+ TSchema
689
+ >
690
+ }
691
+
692
+ /**
693
+ * Maps entity fields to Full types (for explicit mode).
694
+ * These types allow direct value access.
695
+ */
696
+ export type SelectedEntityFields<
697
+ TEntity,
698
+ TSelected,
699
+ TSchema extends Record<string, object> = Record<string, object>,
700
+ > = {
701
+ [K in ScalarKeys<TEntity> & keyof TSelected]: FieldRef<TEntity[K]>
702
+ } & {
703
+ [K in HasManyKeys<TEntity> & keyof TSelected]: TEntity[K] extends (infer U)[]
704
+ ? U extends object
705
+ ? HasManyRef<U, ExtractNestedSelection<TSelected, K> extends (infer S)[] ? S : U, AnyBrand, EntityNameFromType<TSchema, U>, TSchema>
706
+ : never
707
+ : never
708
+ } & {
709
+ [K in HasOneKeys<TEntity> & keyof TSelected]: HasOneAccessor<
710
+ NonNullable<TEntity[K]>,
711
+ ExtractNestedSelection<TSelected, K> extends object ? ExtractNestedSelection<TSelected, K> : NonNullable<TEntity[K]>,
712
+ AnyBrand,
713
+ EntityNameFromType<TSchema, NonNullable<TEntity[K]>>,
714
+ TSchema
715
+ >
716
+ }
717
+
718
+ // ============================================================================
719
+ // Type Extraction Helpers
720
+ // ============================================================================
721
+
722
+ /**
723
+ * Extracts the role map from an EntityAccessor or EntityAccessorBase.
724
+ */
725
+ export type ExtractRoleMap<T> =
726
+ T extends { readonly __roleMap?: infer TRoleMap extends Record<string, object> }
727
+ ? TRoleMap
728
+ : Record<string, object>
729
+
730
+ export type ExtractHasOneEntityName<T> =
731
+ T extends HasOneRef<any, any, any, infer TEntityName, any>
732
+ ? TEntityName
733
+ : T extends HasOneRefBase<any, any, any, infer TEntityName, any>
734
+ ? TEntityName
735
+ : never
736
+
737
+ export type ExtractHasManyEntityName<T> =
738
+ T extends HasManyRef<any, any, any, infer TEntityName, any>
739
+ ? TEntityName
740
+ : T extends HasManyRefBase<any, any, any, infer TEntityName, any>
741
+ ? TEntityName
742
+ : never
743
+