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