@contember/bindx 0.1.5 → 0.1.9
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/adapter/ContemberAdapter.d.ts +11 -14
- package/dist/adapter/ContemberAdapter.d.ts.map +1 -1
- package/dist/brand/ComponentBrand.d.ts +1 -24
- package/dist/brand/ComponentBrand.d.ts.map +1 -1
- package/dist/contember/index.d.ts +1 -2
- package/dist/contember/index.d.ts.map +1 -1
- package/dist/handles/EntityHandle.d.ts +18 -29
- package/dist/handles/EntityHandle.d.ts.map +1 -1
- package/dist/handles/FieldHandle.d.ts +5 -3
- package/dist/handles/FieldHandle.d.ts.map +1 -1
- package/dist/handles/HasManyListHandle.d.ts +4 -3
- package/dist/handles/HasManyListHandle.d.ts.map +1 -1
- package/dist/handles/HasOneHandle.d.ts +33 -36
- package/dist/handles/HasOneHandle.d.ts.map +1 -1
- package/dist/handles/PlaceholderHandle.d.ts +9 -19
- package/dist/handles/PlaceholderHandle.d.ts.map +1 -1
- package/dist/handles/proxyFactory.d.ts +9 -37
- package/dist/handles/proxyFactory.d.ts.map +1 -1
- package/dist/handles/types.d.ts +4 -0
- package/dist/handles/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/persistence/ContemberSchemaMutationAdapter.d.ts +24 -1
- package/dist/persistence/ContemberSchemaMutationAdapter.d.ts.map +1 -1
- package/dist/schema/ContemberSchema.d.ts +1 -97
- package/dist/schema/ContemberSchema.d.ts.map +1 -1
- package/dist/schema/SchemaLoader.d.ts +1 -27
- package/dist/schema/SchemaLoader.d.ts.map +1 -1
- package/dist/schema/SchemaRegistry.d.ts +1 -94
- package/dist/schema/SchemaRegistry.d.ts.map +1 -1
- package/dist/schema/roles.d.ts +1 -61
- package/dist/schema/roles.d.ts.map +1 -1
- package/dist/schema/types.d.ts +1 -178
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/selection/SelectionMetaCollector.d.ts +1 -31
- package/dist/selection/SelectionMetaCollector.d.ts.map +1 -1
- package/dist/selection/SelectionScope.d.ts +1 -76
- package/dist/selection/SelectionScope.d.ts.map +1 -1
- package/dist/selection/buildQuery.d.ts +1 -44
- package/dist/selection/buildQuery.d.ts.map +1 -1
- package/dist/selection/createFragment.d.ts +1 -28
- package/dist/selection/createFragment.d.ts.map +1 -1
- package/dist/selection/createSelectionBuilder.d.ts +1 -17
- package/dist/selection/createSelectionBuilder.d.ts.map +1 -1
- package/dist/selection/queryTypes.d.ts +1 -83
- package/dist/selection/queryTypes.d.ts.map +1 -1
- package/dist/selection/types.d.ts +1 -237
- package/dist/selection/types.d.ts.map +1 -1
- package/dist/utils/aliasGenerator.d.ts +1 -24
- package/dist/utils/aliasGenerator.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/adapter/ContemberAdapter.ts +193 -123
- package/src/brand/ComponentBrand.ts +1 -28
- package/src/contember/index.ts +1 -8
- package/src/handles/EntityHandle.ts +131 -61
- package/src/handles/FieldHandle.ts +19 -3
- package/src/handles/HasManyListHandle.ts +15 -12
- package/src/handles/HasOneHandle.ts +98 -66
- package/src/handles/PlaceholderHandle.ts +26 -24
- package/src/handles/proxyFactory.ts +29 -128
- package/src/handles/types.ts +6 -0
- package/src/index.ts +22 -3
- package/src/persistence/ContemberSchemaMutationAdapter.ts +19 -1
- package/src/schema/ContemberSchema.ts +14 -132
- package/src/schema/SchemaLoader.ts +1 -93
- package/src/schema/SchemaRegistry.ts +1 -286
- package/src/schema/roles.ts +10 -83
- package/src/schema/types.ts +21 -238
- package/src/selection/SelectionMetaCollector.ts +1 -122
- package/src/selection/SelectionScope.ts +1 -208
- package/src/selection/buildQuery.ts +1 -134
- package/src/selection/createFragment.ts +1 -50
- package/src/selection/createSelectionBuilder.ts +1 -283
- package/src/selection/queryTypes.ts +8 -135
- package/src/selection/types.ts +13 -320
- package/src/utils/aliasGenerator.ts +1 -56
|
@@ -12,11 +12,11 @@ import {
|
|
|
12
12
|
} from '../core/actions.js'
|
|
13
13
|
import {
|
|
14
14
|
FIELD_REF_META,
|
|
15
|
-
type HasOneRef,
|
|
16
15
|
type FieldRefMeta,
|
|
17
16
|
type SelectedEntityFields,
|
|
18
17
|
type Unsubscribe,
|
|
19
18
|
type EntityAccessor,
|
|
19
|
+
type HasOneAccessor,
|
|
20
20
|
} from './types.js'
|
|
21
21
|
import { createClientError, type ErrorInput, type FieldError } from '../errors/types.js'
|
|
22
22
|
import type {
|
|
@@ -34,7 +34,7 @@ import type {
|
|
|
34
34
|
} from '../events/types.js'
|
|
35
35
|
import { EntityHandle } from './EntityHandle.js'
|
|
36
36
|
import { PlaceholderHandle } from './PlaceholderHandle.js'
|
|
37
|
-
import { createHandleProxy
|
|
37
|
+
import { createHandleProxy } from './proxyFactory.js'
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -44,35 +44,15 @@ import { createHandleProxy, HAS_ONE_HANDLE_PROPERTIES } from './proxyFactory.js'
|
|
|
44
44
|
* @typeParam TEntity - The full entity type of the related entity
|
|
45
45
|
* @typeParam TSelected - The selected subset of fields (defaults to TEntity for backwards compatibility)
|
|
46
46
|
*/
|
|
47
|
-
export class HasOneHandle<TEntity extends object = object, TSelected = TEntity> extends EntityRelatedHandle
|
|
48
|
-
private
|
|
49
|
-
private
|
|
47
|
+
export class HasOneHandle<TEntity extends object = object, TSelected = TEntity> extends EntityRelatedHandle {
|
|
48
|
+
private entityHandleCacheRaw: EntityHandle<TEntity, TSelected> | null = null
|
|
49
|
+
private entityHandleCacheProxy: EntityAccessor<TEntity, TSelected> | null = null
|
|
50
|
+
private placeholderCacheRaw: PlaceholderHandle<TEntity, TSelected> | null = null
|
|
51
|
+
private placeholderCacheProxy: EntityAccessor<TEntity, TSelected> | null = null
|
|
50
52
|
|
|
51
53
|
/** Runtime brand symbols for validation */
|
|
52
54
|
readonly __brands?: Set<symbol>
|
|
53
55
|
|
|
54
|
-
/** Type brand for schema - placeholder at runtime */
|
|
55
|
-
declare readonly __schema: Record<string, object>
|
|
56
|
-
|
|
57
|
-
// $ aliases - handled by proxy at runtime, declared for TypeScript
|
|
58
|
-
declare readonly $id: string
|
|
59
|
-
declare readonly $isDirty: boolean
|
|
60
|
-
declare readonly $state: 'connected' | 'disconnected' | 'deleted' | 'creating'
|
|
61
|
-
declare readonly $fields: SelectedEntityFields<TEntity, TSelected>
|
|
62
|
-
declare readonly $entity: EntityAccessor<TEntity, TSelected>
|
|
63
|
-
declare readonly $errors: readonly FieldError[]
|
|
64
|
-
declare readonly $hasError: boolean
|
|
65
|
-
declare $connect: (id: string) => void
|
|
66
|
-
declare $disconnect: () => void
|
|
67
|
-
declare $delete: () => void
|
|
68
|
-
declare $reset: () => void
|
|
69
|
-
declare $addError: (error: ErrorInput) => void
|
|
70
|
-
declare $clearErrors: () => void
|
|
71
|
-
declare $onConnect: (listener: EventListener<RelationConnectedEvent>) => Unsubscribe
|
|
72
|
-
declare $onDisconnect: (listener: EventListener<RelationDisconnectedEvent>) => Unsubscribe
|
|
73
|
-
declare $interceptConnect: (interceptor: Interceptor<RelationConnectingEvent>) => Unsubscribe
|
|
74
|
-
declare $interceptDisconnect: (interceptor: Interceptor<RelationDisconnectingEvent>) => Unsubscribe
|
|
75
|
-
|
|
76
56
|
private constructor(
|
|
77
57
|
parentEntityType: string,
|
|
78
58
|
parentEntityId: string,
|
|
@@ -98,11 +78,26 @@ export class HasOneHandle<TEntity extends object = object, TSelected = TEntity>
|
|
|
98
78
|
schema: SchemaRegistry,
|
|
99
79
|
brands?: Set<symbol>,
|
|
100
80
|
selection?: SelectionMeta,
|
|
81
|
+
): HasOneAccessor<TEntity, TSelected> {
|
|
82
|
+
return createHandleProxy<HasOneHandle<TEntity, TSelected>, HasOneAccessor<TEntity, TSelected>>(new HasOneHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, targetType, store, dispatcher, schema, brands, selection), (target) => target.entityRaw.fields)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static createRaw<TEntity extends object = object, TSelected = TEntity>(
|
|
86
|
+
parentEntityType: string,
|
|
87
|
+
parentEntityId: string,
|
|
88
|
+
fieldName: string,
|
|
89
|
+
targetType: string,
|
|
90
|
+
store: SnapshotStore,
|
|
91
|
+
dispatcher: ActionDispatcher,
|
|
92
|
+
schema: SchemaRegistry,
|
|
93
|
+
brands?: Set<symbol>,
|
|
94
|
+
selection?: SelectionMeta,
|
|
101
95
|
): HasOneHandle<TEntity, TSelected> {
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
return new HasOneHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, targetType, store, dispatcher, schema, brands, selection)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static wrapProxy<TEntity extends object, TSelected>(handle: HasOneHandle<TEntity, TSelected>): HasOneAccessor<TEntity, TSelected> {
|
|
100
|
+
return createHandleProxy<HasOneHandle<TEntity, TSelected>, HasOneAccessor<TEntity, TSelected>>(handle, (target) => target.entityRaw.fields)
|
|
106
101
|
}
|
|
107
102
|
|
|
108
103
|
/**
|
|
@@ -198,21 +193,18 @@ export class HasOneHandle<TEntity extends object = object, TSelected = TEntity>
|
|
|
198
193
|
}
|
|
199
194
|
|
|
200
195
|
/**
|
|
201
|
-
* Gets the related entity
|
|
202
|
-
*
|
|
203
|
-
*
|
|
196
|
+
* Gets the raw (unproxied) related entity handle.
|
|
197
|
+
* Returns raw EntityHandle or raw PlaceholderHandle.
|
|
198
|
+
* Used internally to avoid going through the proxy layer.
|
|
204
199
|
*/
|
|
205
|
-
get
|
|
200
|
+
get entityRaw(): EntityHandle<TEntity, TSelected> | PlaceholderHandle<TEntity, TSelected> {
|
|
206
201
|
const id = this.relatedId
|
|
207
202
|
|
|
208
203
|
if (id) {
|
|
209
|
-
// Ensure the related entity has a snapshot in the store
|
|
210
|
-
// (it may be embedded in the parent entity's data)
|
|
211
204
|
this.ensureRelatedEntitySnapshot(id)
|
|
212
205
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this.entityHandleCache = EntityHandle.create<TEntity, TSelected>(
|
|
206
|
+
if (!this.entityHandleCacheRaw || this.entityHandleCacheRaw.id !== id) {
|
|
207
|
+
this.entityHandleCacheRaw = EntityHandle.createRaw<TEntity, TSelected>(
|
|
216
208
|
id,
|
|
217
209
|
this.targetType,
|
|
218
210
|
this.store,
|
|
@@ -221,14 +213,13 @@ export class HasOneHandle<TEntity extends object = object, TSelected = TEntity>
|
|
|
221
213
|
this.__brands,
|
|
222
214
|
this.selection,
|
|
223
215
|
)
|
|
216
|
+
this.entityHandleCacheProxy = EntityHandle.wrapProxy(this.entityHandleCacheRaw)
|
|
224
217
|
}
|
|
225
|
-
|
|
226
|
-
return this.entityHandleCache as unknown as EntityAccessor<TEntity, TSelected>
|
|
218
|
+
return this.entityHandleCacheRaw
|
|
227
219
|
}
|
|
228
220
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.placeholderCache = PlaceholderHandle.create<TEntity, TSelected>(
|
|
221
|
+
if (!this.placeholderCacheRaw) {
|
|
222
|
+
this.placeholderCacheRaw = PlaceholderHandle.createRaw<TEntity, TSelected>(
|
|
232
223
|
this.entityType,
|
|
233
224
|
this.entityId,
|
|
234
225
|
this.fieldName,
|
|
@@ -237,9 +228,38 @@ export class HasOneHandle<TEntity extends object = object, TSelected = TEntity>
|
|
|
237
228
|
this.dispatcher,
|
|
238
229
|
this.__brands,
|
|
239
230
|
)
|
|
231
|
+
this.placeholderCacheProxy = PlaceholderHandle.wrapProxy(this.placeholderCacheRaw)
|
|
232
|
+
}
|
|
233
|
+
return this.placeholderCacheRaw
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Gets the related entity accessor with direct field access.
|
|
238
|
+
* Implements HasOneRef.$entity - returns EntityAccessor for the related entity.
|
|
239
|
+
* Returns PlaceholderHandle (with placeholder ID) if the relation is disconnected.
|
|
240
|
+
*/
|
|
241
|
+
get entity(): EntityAccessor<TEntity, TSelected> {
|
|
242
|
+
const id = this.relatedId
|
|
243
|
+
|
|
244
|
+
if (id) {
|
|
245
|
+
// Ensure the related entity has a snapshot in the store
|
|
246
|
+
// (it may be embedded in the parent entity's data)
|
|
247
|
+
this.ensureRelatedEntitySnapshot(id)
|
|
248
|
+
|
|
249
|
+
// Connected - return real entity handle (populate raw cache via entityRaw if needed)
|
|
250
|
+
if (!this.entityHandleCacheRaw || this.entityHandleCacheRaw.id !== id) {
|
|
251
|
+
// entityRaw populates both raw and proxy caches
|
|
252
|
+
this.entityRaw
|
|
253
|
+
}
|
|
254
|
+
return this.entityHandleCacheProxy!
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Disconnected - return placeholder handle
|
|
258
|
+
if (!this.placeholderCacheRaw) {
|
|
259
|
+
// entityRaw populates both raw and proxy caches
|
|
260
|
+
this.entityRaw
|
|
240
261
|
}
|
|
241
|
-
|
|
242
|
-
return this.placeholderCache as unknown as EntityAccessor<TEntity, TSelected>
|
|
262
|
+
return this.placeholderCacheProxy!
|
|
243
263
|
}
|
|
244
264
|
|
|
245
265
|
/**
|
|
@@ -288,6 +308,15 @@ export class HasOneHandle<TEntity extends object = object, TSelected = TEntity>
|
|
|
288
308
|
)
|
|
289
309
|
}
|
|
290
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Checks if the related entity is currently being persisted.
|
|
313
|
+
*/
|
|
314
|
+
get isPersisting(): boolean {
|
|
315
|
+
const id = this.relatedId
|
|
316
|
+
if (!id) return false
|
|
317
|
+
return this.store.isPersisting(this.targetType, id)
|
|
318
|
+
}
|
|
319
|
+
|
|
291
320
|
/**
|
|
292
321
|
* Checks if the relation is dirty.
|
|
293
322
|
*/
|
|
@@ -349,8 +378,11 @@ export class HasOneHandle<TEntity extends object = object, TSelected = TEntity>
|
|
|
349
378
|
*/
|
|
350
379
|
override dispose(): void {
|
|
351
380
|
super.dispose()
|
|
352
|
-
this.
|
|
353
|
-
this.
|
|
381
|
+
this.entityHandleCacheRaw?.dispose()
|
|
382
|
+
this.entityHandleCacheRaw = null
|
|
383
|
+
this.entityHandleCacheProxy = null
|
|
384
|
+
this.placeholderCacheRaw = null
|
|
385
|
+
this.placeholderCacheProxy = null
|
|
354
386
|
}
|
|
355
387
|
|
|
356
388
|
/**
|
|
@@ -454,47 +486,47 @@ export class HasOneHandle<TEntity extends object = object, TSelected = TEntity>
|
|
|
454
486
|
}
|
|
455
487
|
|
|
456
488
|
// ==================== EntityRef-compatible Properties ====================
|
|
457
|
-
// These
|
|
489
|
+
// These delegate to entityRaw so that proxy resolution ($data→data, $isNew→isNew, etc.) works correctly.
|
|
458
490
|
|
|
459
|
-
/** Raw data snapshot of the related entity - delegates to
|
|
460
|
-
get
|
|
491
|
+
/** Raw data snapshot of the related entity - delegates to entityRaw */
|
|
492
|
+
get data(): TSelected | null { return this.entityRaw.data }
|
|
461
493
|
|
|
462
|
-
/** Whether this entity is new - delegates to
|
|
463
|
-
get
|
|
494
|
+
/** Whether this entity is new - delegates to entityRaw */
|
|
495
|
+
get isNew(): boolean { return this.entityRaw.isNew }
|
|
464
496
|
|
|
465
|
-
/** Server-assigned ID after persistence - delegates to
|
|
466
|
-
get
|
|
497
|
+
/** Server-assigned ID after persistence - delegates to entityRaw */
|
|
498
|
+
get persistedId(): string | null { return this.entityRaw.persistedId }
|
|
467
499
|
|
|
468
500
|
/** Type brand for entity name */
|
|
469
501
|
get __entityName(): string { return this.targetType }
|
|
470
502
|
|
|
471
|
-
/** Clear all errors - delegates to
|
|
472
|
-
|
|
503
|
+
/** Clear all errors - delegates to entityRaw */
|
|
504
|
+
clearAllErrors(): void { this.entityRaw.clearAllErrors() }
|
|
473
505
|
|
|
474
506
|
/** Subscribe to any event on the related entity */
|
|
475
|
-
|
|
507
|
+
on<E extends AfterEventTypes>(
|
|
476
508
|
eventType: E,
|
|
477
509
|
listener: EventListener<EventTypeMap[E]>,
|
|
478
510
|
): Unsubscribe {
|
|
479
|
-
return this.
|
|
511
|
+
return this.entityRaw.on(eventType, listener)
|
|
480
512
|
}
|
|
481
513
|
|
|
482
514
|
/** Intercept any before event on the related entity */
|
|
483
|
-
|
|
515
|
+
intercept<E extends BeforeEventTypes>(
|
|
484
516
|
eventType: E,
|
|
485
517
|
interceptor: Interceptor<EventTypeMap[E]>,
|
|
486
518
|
): Unsubscribe {
|
|
487
|
-
return this.
|
|
519
|
+
return this.entityRaw.intercept(eventType, interceptor)
|
|
488
520
|
}
|
|
489
521
|
|
|
490
522
|
/** Subscribe to persist success events on the related entity */
|
|
491
|
-
|
|
492
|
-
return this.
|
|
523
|
+
onPersisted(listener: EventListener<EntityPersistedEvent>): Unsubscribe {
|
|
524
|
+
return this.entityRaw.onPersisted(listener)
|
|
493
525
|
}
|
|
494
526
|
|
|
495
527
|
/** Intercept persist on the related entity */
|
|
496
|
-
|
|
497
|
-
return this.
|
|
528
|
+
interceptPersisting(interceptor: Interceptor<EntityPersistingEvent>): Unsubscribe {
|
|
529
|
+
return this.entityRaw.interceptPersisting(interceptor)
|
|
498
530
|
}
|
|
499
531
|
|
|
500
532
|
}
|
|
@@ -3,10 +3,10 @@ import type { SnapshotStore } from '../store/SnapshotStore.js'
|
|
|
3
3
|
import { generatePlaceholderId } from '../store/SnapshotStore.js'
|
|
4
4
|
import {
|
|
5
5
|
FIELD_REF_META,
|
|
6
|
-
type EntityRef,
|
|
7
6
|
type SelectedEntityFields,
|
|
8
7
|
type FieldRefMeta,
|
|
9
8
|
type Unsubscribe,
|
|
9
|
+
type EntityAccessor,
|
|
10
10
|
} from './types.js'
|
|
11
11
|
import type { ErrorInput, FieldError } from '../errors/types.js'
|
|
12
12
|
import type {
|
|
@@ -28,34 +28,13 @@ import { createAliasProxy } from './proxyFactory.js'
|
|
|
28
28
|
* @typeParam TEntity - The full entity type
|
|
29
29
|
* @typeParam TSelected - The selected subset of fields
|
|
30
30
|
*/
|
|
31
|
-
export class PlaceholderHandle<TEntity extends object = object, TSelected = TEntity>
|
|
32
|
-
implements EntityRef<TEntity, TSelected>
|
|
33
|
-
{
|
|
31
|
+
export class PlaceholderHandle<TEntity extends object = object, TSelected = TEntity> {
|
|
34
32
|
/** Runtime brand symbols for validation */
|
|
35
33
|
readonly __brands?: Set<symbol>
|
|
36
34
|
|
|
37
|
-
/** Type brand for schema - placeholder at runtime */
|
|
38
|
-
declare readonly __schema: Record<string, object>
|
|
39
|
-
|
|
40
35
|
/** Placeholder ID for this handle */
|
|
41
36
|
private readonly placeholderId: string
|
|
42
37
|
|
|
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
38
|
private constructor(
|
|
60
39
|
private readonly parentEntityType: string,
|
|
61
40
|
private readonly parentEntityId: string,
|
|
@@ -77,8 +56,24 @@ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEnt
|
|
|
77
56
|
store: SnapshotStore,
|
|
78
57
|
dispatcher: ActionDispatcher,
|
|
79
58
|
brands?: Set<symbol>,
|
|
59
|
+
): EntityAccessor<TEntity, TSelected> {
|
|
60
|
+
return PlaceholderHandle.wrapProxy(new PlaceholderHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, targetType, store, dispatcher, brands))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static createRaw<TEntity extends object = object, TSelected = TEntity>(
|
|
64
|
+
parentEntityType: string,
|
|
65
|
+
parentEntityId: string,
|
|
66
|
+
fieldName: string,
|
|
67
|
+
targetType: string,
|
|
68
|
+
store: SnapshotStore,
|
|
69
|
+
dispatcher: ActionDispatcher,
|
|
70
|
+
brands?: Set<symbol>,
|
|
80
71
|
): PlaceholderHandle<TEntity, TSelected> {
|
|
81
|
-
return
|
|
72
|
+
return new PlaceholderHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, targetType, store, dispatcher, brands)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static wrapProxy<TEntity extends object, TSelected>(handle: PlaceholderHandle<TEntity, TSelected>): EntityAccessor<TEntity, TSelected> {
|
|
76
|
+
return createAliasProxy<PlaceholderHandle<TEntity, TSelected>, EntityAccessor<TEntity, TSelected>>(handle)
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
/**
|
|
@@ -115,6 +110,13 @@ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEnt
|
|
|
115
110
|
return relation ? Object.keys(relation.placeholderData).length > 0 : false
|
|
116
111
|
}
|
|
117
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Placeholder entities are never being persisted.
|
|
115
|
+
*/
|
|
116
|
+
get isPersisting(): boolean {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
118
120
|
/**
|
|
119
121
|
* Placeholder entities are always new (not yet persisted).
|
|
120
122
|
*/
|
|
@@ -8,132 +8,40 @@
|
|
|
8
8
|
* The $ prefix convention allows handle properties to avoid collision with entity field names:
|
|
9
9
|
* - `entity.title` → field access (returns FieldHandle)
|
|
10
10
|
* - `entity.$isDirty` → handle property
|
|
11
|
+
* - `entity.id` → entity ID (special case, always passes through)
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import { FIELD_REF_META } from './types.js'
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* Properties that always pass through to the handle, never treated as field access.
|
|
18
|
+
* Kept minimal to avoid collisions with entity field names.
|
|
18
19
|
*/
|
|
19
|
-
const
|
|
20
|
-
// Entity identity — id
|
|
20
|
+
const HANDLE_PASSTHROUGH_PROPERTIES = new Set<string | symbol>([
|
|
21
|
+
// Entity identity — `entity.id` must return the entity ID string, not a FieldHandle
|
|
21
22
|
'id',
|
|
22
|
-
//
|
|
23
|
-
'__entityType', '__entityName', '__brands', '__schema',
|
|
24
|
-
// Symbol
|
|
23
|
+
// Symbol for field reference metadata
|
|
25
24
|
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
|
-
'type', '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
25
|
])
|
|
95
26
|
|
|
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
27
|
/**
|
|
123
28
|
* Creates a proxy around a handle that supports direct field access.
|
|
124
29
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
30
|
+
* Resolution order:
|
|
31
|
+
* 1. Symbols → pass through to handle
|
|
32
|
+
* 2. `id` / FIELD_REF_META → pass through to handle
|
|
33
|
+
* 3. `$xxx` → strip `$`, access handle property
|
|
34
|
+
* 4. Everything else → field access (returns FieldHandle/HasOneHandle/HasManyListHandle)
|
|
128
35
|
*
|
|
129
36
|
* @param handle - The handle instance to wrap
|
|
130
|
-
* @param
|
|
37
|
+
* @param getFields - Function to get the fields object from the handle
|
|
131
38
|
* @returns Proxied handle with direct field access support
|
|
132
39
|
*/
|
|
133
|
-
export function createHandleProxy<T extends object>(
|
|
40
|
+
export function createHandleProxy<T extends object, TResult = T>(
|
|
134
41
|
handle: T,
|
|
135
|
-
|
|
136
|
-
):
|
|
42
|
+
getFields: (target: T) => object,
|
|
43
|
+
): TResult {
|
|
44
|
+
// The proxy adds $ alias support and field access, making the handle satisfy the public type TResult at runtime
|
|
137
45
|
return new Proxy(handle, {
|
|
138
46
|
get(target, prop, _receiver) {
|
|
139
47
|
// Symbols and non-string props - pass through
|
|
@@ -141,41 +49,34 @@ export function createHandleProxy<T extends object>(
|
|
|
141
49
|
return Reflect.get(target, prop, target)
|
|
142
50
|
}
|
|
143
51
|
|
|
52
|
+
// Special properties that always pass through
|
|
53
|
+
if (HANDLE_PASSTHROUGH_PROPERTIES.has(prop)) {
|
|
54
|
+
return Reflect.get(target, prop, target)
|
|
55
|
+
}
|
|
56
|
+
|
|
144
57
|
// $ prefixed - strip $ and access handle property
|
|
145
58
|
if (prop.startsWith('$')) {
|
|
146
59
|
const realProp = prop.slice(1)
|
|
147
|
-
// Use target as receiver so getters use target as `this`, not the Proxy
|
|
148
60
|
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
61
|
if (typeof value === 'function') {
|
|
160
62
|
return value.bind(target)
|
|
161
63
|
}
|
|
162
64
|
return value
|
|
163
65
|
}
|
|
164
66
|
|
|
165
|
-
//
|
|
166
|
-
const fields =
|
|
67
|
+
// Everything else → field access
|
|
68
|
+
const fields = getFields(target)
|
|
167
69
|
return (fields as Record<string, unknown>)[prop]
|
|
168
70
|
},
|
|
169
71
|
|
|
170
72
|
has(target, prop) {
|
|
171
|
-
if (typeof prop === 'string' && !
|
|
172
|
-
|
|
173
|
-
const fields = config.getFields(target)
|
|
73
|
+
if (typeof prop === 'string' && !HANDLE_PASSTHROUGH_PROPERTIES.has(prop) && !prop.startsWith('$')) {
|
|
74
|
+
const fields = getFields(target)
|
|
174
75
|
return prop in fields
|
|
175
76
|
}
|
|
176
77
|
return Reflect.has(target, prop)
|
|
177
78
|
},
|
|
178
|
-
})
|
|
79
|
+
}) as unknown as TResult
|
|
179
80
|
}
|
|
180
81
|
|
|
181
82
|
/**
|
|
@@ -188,7 +89,8 @@ export function createHandleProxy<T extends object>(
|
|
|
188
89
|
* @param handle - The handle instance to wrap
|
|
189
90
|
* @returns Proxied handle with $ alias support
|
|
190
91
|
*/
|
|
191
|
-
export function createAliasProxy<T extends object>(handle: T):
|
|
92
|
+
export function createAliasProxy<T extends object, TResult = T>(handle: T): TResult {
|
|
93
|
+
// The proxy adds $ alias support, making the handle satisfy the public type TResult at runtime
|
|
192
94
|
return new Proxy(handle, {
|
|
193
95
|
get(target, prop, _receiver) {
|
|
194
96
|
// Symbols and non-string props - pass through
|
|
@@ -200,7 +102,6 @@ export function createAliasProxy<T extends object>(handle: T): T {
|
|
|
200
102
|
if (prop.startsWith('$')) {
|
|
201
103
|
const realProp = prop.slice(1)
|
|
202
104
|
const value = Reflect.get(target, realProp, target)
|
|
203
|
-
// Bind methods to preserve `this`
|
|
204
105
|
if (typeof value === 'function') {
|
|
205
106
|
return value.bind(target)
|
|
206
107
|
}
|
|
@@ -214,5 +115,5 @@ export function createAliasProxy<T extends object>(handle: T): T {
|
|
|
214
115
|
}
|
|
215
116
|
return value
|
|
216
117
|
},
|
|
217
|
-
})
|
|
118
|
+
}) as unknown as TResult
|
|
218
119
|
}
|
package/src/handles/types.ts
CHANGED
|
@@ -396,6 +396,9 @@ export interface HasOneRefBase<
|
|
|
396
396
|
/** Whether this entity is new */
|
|
397
397
|
readonly $isNew: boolean
|
|
398
398
|
|
|
399
|
+
/** Whether entity is currently being persisted */
|
|
400
|
+
readonly $isPersisting: boolean
|
|
401
|
+
|
|
399
402
|
/** Server-assigned ID after persistence */
|
|
400
403
|
readonly $persistedId: string | null
|
|
401
404
|
|
|
@@ -539,6 +542,9 @@ export interface EntityRefBase<
|
|
|
539
542
|
/** Whether entity is dirty */
|
|
540
543
|
readonly $isDirty: boolean
|
|
541
544
|
|
|
545
|
+
/** Whether entity is currently being persisted */
|
|
546
|
+
readonly $isPersisting: boolean
|
|
547
|
+
|
|
542
548
|
/** Server-assigned ID after persistence */
|
|
543
549
|
readonly $persistedId: string | null
|
|
544
550
|
|
package/src/index.ts
CHANGED
|
@@ -224,7 +224,7 @@ export { deepEqual } from './utils/deepEqual.js'
|
|
|
224
224
|
export { generateHasManyAlias } from './utils/aliasGenerator.js'
|
|
225
225
|
|
|
226
226
|
// Contember integration
|
|
227
|
-
export { MutationCollector, ContemberSchemaMutationAdapter } from './contember/index.js'
|
|
227
|
+
export { MutationCollector, ContemberSchemaMutationAdapter, type SchemaNames } from './contember/index.js'
|
|
228
228
|
export type { MutationSchemaProvider, EntityMutationResult } from './contember/index.js'
|
|
229
229
|
|
|
230
230
|
// Undo/Redo
|
|
@@ -328,8 +328,27 @@ export {
|
|
|
328
328
|
} from './errors/index.js'
|
|
329
329
|
|
|
330
330
|
|
|
331
|
-
// Re-export
|
|
332
|
-
export
|
|
331
|
+
// Re-export bindx-client types
|
|
332
|
+
export {
|
|
333
|
+
ContentClient,
|
|
334
|
+
type ContentClientOptions,
|
|
335
|
+
type ContentQuery,
|
|
336
|
+
type ContentMutation,
|
|
337
|
+
ContentOperation,
|
|
338
|
+
type MutationResult as ClientMutationResult,
|
|
339
|
+
MutationFailedError,
|
|
340
|
+
type QuerySpecContext,
|
|
341
|
+
querySpecToGraphQl,
|
|
342
|
+
buildGetArgs,
|
|
343
|
+
buildListArgs,
|
|
344
|
+
buildCreateArgs,
|
|
345
|
+
buildUpdateArgs,
|
|
346
|
+
buildDeleteArgs,
|
|
347
|
+
buildMutationSelection,
|
|
348
|
+
mutationFragments,
|
|
349
|
+
unwrapPaginateResult,
|
|
350
|
+
} from '@contember/bindx-client'
|
|
351
|
+
export { qb } from '@contember/bindx-client'
|
|
333
352
|
|
|
334
353
|
// DataView / DataGrid
|
|
335
354
|
export type {
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Runtime schema names format (from Contember API / generated code).
|
|
3
|
+
*/
|
|
4
|
+
export interface SchemaNames {
|
|
5
|
+
readonly entities: {
|
|
6
|
+
readonly [entityName: string]: {
|
|
7
|
+
readonly name: string
|
|
8
|
+
readonly scalars: readonly string[]
|
|
9
|
+
readonly fields: {
|
|
10
|
+
readonly [fieldName: string]:
|
|
11
|
+
| { readonly type: 'column'; readonly enumName?: string; readonly columnType?: string }
|
|
12
|
+
| { readonly type: 'many' | 'one'; readonly entity: string }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
readonly enums?: {
|
|
17
|
+
readonly [enumName: string]: readonly string[]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
2
20
|
import type { MutationSchemaProvider } from './MutationSchemaProvider.js'
|
|
3
21
|
|
|
4
22
|
/**
|