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