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