@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,283 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SELECTION_META,
|
|
3
|
+
type FluentFragment,
|
|
4
|
+
type SelectionBuilder,
|
|
5
|
+
type SelectionFieldMeta,
|
|
6
|
+
type SelectionMeta,
|
|
7
|
+
type HasManyOptions,
|
|
8
|
+
} from './types.js'
|
|
9
|
+
import { mergeSelections, SelectionMetaCollector } from './SelectionMetaCollector.js'
|
|
10
|
+
import { generateHasManyAlias } from '../utils/aliasGenerator.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a fluent selection builder for an entity type.
|
|
14
|
+
* Uses Proxy to intercept method calls and build selection metadata.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const builder = createSelectionBuilder<Article>()
|
|
19
|
+
* const result = builder.id().title().author(a => a.name())
|
|
20
|
+
* const meta = result[SELECTION_META]
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function createSelectionBuilder<TEntity, TSelected extends object = object, THasManyParams extends object = object>(
|
|
24
|
+
initialMeta: SelectionMeta = { fields: new Map() },
|
|
25
|
+
): SelectionBuilder<TEntity, TSelected, THasManyParams> {
|
|
26
|
+
const handler: ProxyHandler<object> = {
|
|
27
|
+
get(_target, prop: string | symbol) {
|
|
28
|
+
// Return metadata for introspection
|
|
29
|
+
if (prop === SELECTION_META) {
|
|
30
|
+
return initialMeta
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Type brand accessor
|
|
34
|
+
if (prop === '__selected') {
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Skip internal symbols
|
|
39
|
+
if (typeof prop === 'symbol') {
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Return a function that handles the field selection
|
|
44
|
+
return createFieldMethod<TEntity, TSelected, THasManyParams>(prop, initialMeta)
|
|
45
|
+
},
|
|
46
|
+
has(_target, prop: string | symbol) {
|
|
47
|
+
if (prop === SELECTION_META) {
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
return false
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return new Proxy({}, handler) as SelectionBuilder<TEntity, TSelected, THasManyParams>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates a method for selecting a specific field.
|
|
59
|
+
* The method signature depends on the field type (scalar, has-one, has-many).
|
|
60
|
+
*/
|
|
61
|
+
function createFieldMethod<TEntity, TSelected extends object, THasManyParams extends object>(
|
|
62
|
+
fieldName: string,
|
|
63
|
+
currentMeta: SelectionMeta,
|
|
64
|
+
) {
|
|
65
|
+
return function selectField(...args: unknown[]): SelectionBuilder<TEntity, object, object> {
|
|
66
|
+
// Parse arguments to determine what kind of selection this is
|
|
67
|
+
const { options, selector, fragment, fragments } = parseFieldArgs(args)
|
|
68
|
+
|
|
69
|
+
// Build field metadata
|
|
70
|
+
const fieldMeta: SelectionFieldMeta = {
|
|
71
|
+
fieldName,
|
|
72
|
+
alias: fieldName, // Will be updated below
|
|
73
|
+
path: [fieldName],
|
|
74
|
+
isRelation: false,
|
|
75
|
+
isArray: false,
|
|
76
|
+
nested: undefined,
|
|
77
|
+
hasManyParams: undefined,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle relation with callback
|
|
81
|
+
if (typeof selector === 'function') {
|
|
82
|
+
const nestedBuilder = createSelectionBuilder<unknown>()
|
|
83
|
+
const resultBuilder = selector(nestedBuilder) as SelectionBuilder<unknown, object, object>
|
|
84
|
+
fieldMeta.nested = resultBuilder[SELECTION_META]
|
|
85
|
+
fieldMeta.isRelation = true
|
|
86
|
+
|
|
87
|
+
// Check if this looks like a has-many (options has filter/orderBy/limit)
|
|
88
|
+
if (options && isHasManyOptions(options)) {
|
|
89
|
+
fieldMeta.isArray = true
|
|
90
|
+
fieldMeta.hasManyParams = {
|
|
91
|
+
filter: (options as HasManyOptions).filter,
|
|
92
|
+
orderBy: (options as HasManyOptions).orderBy,
|
|
93
|
+
limit: (options as HasManyOptions).limit,
|
|
94
|
+
offset: (options as HasManyOptions).offset,
|
|
95
|
+
totalCount: (options as HasManyOptions).totalCount,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle relation with single fragment
|
|
101
|
+
if (fragment && isFluentFragment(fragment)) {
|
|
102
|
+
fieldMeta.nested = fragment.__meta
|
|
103
|
+
fieldMeta.isRelation = true
|
|
104
|
+
|
|
105
|
+
// Check has-many params
|
|
106
|
+
if (options && isHasManyOptions(options)) {
|
|
107
|
+
fieldMeta.isArray = true
|
|
108
|
+
fieldMeta.hasManyParams = {
|
|
109
|
+
filter: (options as HasManyOptions).filter,
|
|
110
|
+
orderBy: (options as HasManyOptions).orderBy,
|
|
111
|
+
limit: (options as HasManyOptions).limit,
|
|
112
|
+
offset: (options as HasManyOptions).offset,
|
|
113
|
+
totalCount: (options as HasManyOptions).totalCount,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Handle relation with multiple fragments (merged)
|
|
119
|
+
if (fragments && fragments.length > 0) {
|
|
120
|
+
// Merge all fragment selections into one
|
|
121
|
+
const mergedSelection = new SelectionMetaCollector()
|
|
122
|
+
for (const frag of fragments) {
|
|
123
|
+
mergeSelections(mergedSelection, frag.__meta)
|
|
124
|
+
}
|
|
125
|
+
fieldMeta.nested = mergedSelection
|
|
126
|
+
fieldMeta.isRelation = true
|
|
127
|
+
|
|
128
|
+
// Check has-many params
|
|
129
|
+
if (options && isHasManyOptions(options)) {
|
|
130
|
+
fieldMeta.isArray = true
|
|
131
|
+
fieldMeta.hasManyParams = {
|
|
132
|
+
filter: (options as HasManyOptions).filter,
|
|
133
|
+
orderBy: (options as HasManyOptions).orderBy,
|
|
134
|
+
limit: (options as HasManyOptions).limit,
|
|
135
|
+
offset: (options as HasManyOptions).offset,
|
|
136
|
+
totalCount: (options as HasManyOptions).totalCount,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Determine alias:
|
|
142
|
+
// 1. Explicit alias from options takes priority
|
|
143
|
+
// 2. For has-many with params, auto-generate alias
|
|
144
|
+
// 3. Otherwise, use fieldName
|
|
145
|
+
const explicitAlias = (options as { as?: string } | undefined)?.as
|
|
146
|
+
if (explicitAlias) {
|
|
147
|
+
fieldMeta.alias = explicitAlias
|
|
148
|
+
} else if (fieldMeta.isArray && fieldMeta.hasManyParams) {
|
|
149
|
+
// Auto-generate alias for has-many with params
|
|
150
|
+
fieldMeta.alias = generateHasManyAlias(fieldName, fieldMeta.hasManyParams)
|
|
151
|
+
}
|
|
152
|
+
// else alias is already set to fieldName
|
|
153
|
+
|
|
154
|
+
// If it's a relation call without options but first arg is callback/fragment
|
|
155
|
+
// and there's no filter/orderBy, check if it's has-many by detecting array at runtime
|
|
156
|
+
// This is handled by the type system - if the field type is Array<T>, it will use HasManyMethod
|
|
157
|
+
|
|
158
|
+
// Create new metadata with this field added
|
|
159
|
+
const newMeta: SelectionMeta = {
|
|
160
|
+
fields: new Map(currentMeta.fields),
|
|
161
|
+
}
|
|
162
|
+
newMeta.fields.set(fieldMeta.alias, fieldMeta)
|
|
163
|
+
|
|
164
|
+
// Return new builder with accumulated selection
|
|
165
|
+
return createSelectionBuilder<TEntity, TSelected, THasManyParams>(newMeta)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Type guard for HasManyOptions
|
|
171
|
+
*/
|
|
172
|
+
function isHasManyOptions(value: unknown): value is HasManyOptions {
|
|
173
|
+
if (typeof value !== 'object' || value === null) {
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
return 'filter' in value || 'orderBy' in value || 'limit' in value || 'offset' in value
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Type guard for FluentFragment
|
|
181
|
+
*/
|
|
182
|
+
function isFluentFragment(value: unknown): value is FluentFragment<unknown, unknown> {
|
|
183
|
+
return (
|
|
184
|
+
value !== null &&
|
|
185
|
+
typeof value === 'object' &&
|
|
186
|
+
'__isFragment' in value &&
|
|
187
|
+
(value as FluentFragment<unknown, unknown>).__isFragment === true
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Parses the variable arguments to a field method.
|
|
193
|
+
*
|
|
194
|
+
* Possible call patterns:
|
|
195
|
+
* - e.title() -> scalar, no args
|
|
196
|
+
* - e.title({ as: 'x' }) -> scalar with alias
|
|
197
|
+
* - e.author(a => a.name()) -> has-one with callback
|
|
198
|
+
* - e.author(AuthorFragment) -> has-one with fragment
|
|
199
|
+
* - e.author(Frag1, Frag2, ...) -> has-one with multiple fragments (merged)
|
|
200
|
+
* - e.author({ as: 'x' }, a => a.name())-> has-one with alias + callback
|
|
201
|
+
* - e.author({ as: 'x' }, AuthorFrag) -> has-one with alias + fragment
|
|
202
|
+
* - e.author({ as: 'x' }, Frag1, Frag2) -> has-one with alias + multiple fragments
|
|
203
|
+
* - e.tags(t => t.name()) -> has-many with callback
|
|
204
|
+
* - e.tags(TagFragment) -> has-many with fragment
|
|
205
|
+
* - e.tags(Frag1, Frag2, ...) -> has-many with multiple fragments (merged)
|
|
206
|
+
* - e.tags({ filter: {} }, t => t.name()) -> has-many with options + callback
|
|
207
|
+
* - e.tags({ filter: {} }, TagFragment) -> has-many with options + fragment
|
|
208
|
+
* - e.tags({ filter: {} }, Frag1, Frag2) -> has-many with options + multiple fragments
|
|
209
|
+
*/
|
|
210
|
+
function parseFieldArgs(args: unknown[]): {
|
|
211
|
+
options?: Record<string, unknown>
|
|
212
|
+
selector?: (builder: SelectionBuilder<unknown>) => SelectionBuilder<unknown, object, object>
|
|
213
|
+
fragment?: FluentFragment<unknown, unknown>
|
|
214
|
+
fragments?: FluentFragment<unknown, unknown>[]
|
|
215
|
+
} {
|
|
216
|
+
if (args.length === 0) {
|
|
217
|
+
// Just field name, no args: e.title()
|
|
218
|
+
return {}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (args.length === 1) {
|
|
222
|
+
const arg = args[0]
|
|
223
|
+
if (typeof arg === 'function') {
|
|
224
|
+
// Selector callback: e.author(a => a.name())
|
|
225
|
+
return { selector: arg as (builder: SelectionBuilder<unknown>) => SelectionBuilder<unknown, object, object> }
|
|
226
|
+
}
|
|
227
|
+
if (typeof arg === 'object' && arg !== null) {
|
|
228
|
+
if (isFluentFragment(arg)) {
|
|
229
|
+
// Fragment: e.author(AuthorFragment)
|
|
230
|
+
return { fragment: arg }
|
|
231
|
+
}
|
|
232
|
+
// Options without selector: e.title({ as: 'caption' })
|
|
233
|
+
return { options: arg as Record<string, unknown> }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check for multiple fragments: e.author(Frag1, Frag2, ...)
|
|
238
|
+
if (args.length >= 2 && args.every(isFluentFragment)) {
|
|
239
|
+
return { fragments: args as FluentFragment<unknown, unknown>[] }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (args.length >= 2) {
|
|
243
|
+
const [first, ...rest] = args
|
|
244
|
+
|
|
245
|
+
// Check if first is options object
|
|
246
|
+
if (typeof first === 'object' && first !== null && !isFluentFragment(first)) {
|
|
247
|
+
const second = rest[0]
|
|
248
|
+
|
|
249
|
+
if (typeof second === 'function') {
|
|
250
|
+
// Options + selector: e.tags({ filter: {...} }, t => t.name())
|
|
251
|
+
return {
|
|
252
|
+
options: first as Record<string, unknown>,
|
|
253
|
+
selector: second as (builder: SelectionBuilder<unknown>) => SelectionBuilder<unknown, object, object>,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check for options + multiple fragments
|
|
258
|
+
if (rest.every(isFluentFragment)) {
|
|
259
|
+
if (rest.length === 1) {
|
|
260
|
+
// Options + single fragment: e.tags({ filter: {...} }, TagFragment)
|
|
261
|
+
return {
|
|
262
|
+
options: first as Record<string, unknown>,
|
|
263
|
+
fragment: rest[0] as FluentFragment<unknown, unknown>,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Options + multiple fragments: e.tags({ filter: {} }, Frag1, Frag2)
|
|
267
|
+
return {
|
|
268
|
+
options: first as Record<string, unknown>,
|
|
269
|
+
fragments: rest as FluentFragment<unknown, unknown>[],
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Extracts SelectionMeta from a builder
|
|
280
|
+
*/
|
|
281
|
+
export function getSelectionMeta(builder: SelectionBuilder<unknown, object, object>): SelectionMeta {
|
|
282
|
+
return builder[SELECTION_META]
|
|
283
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export {
|
|
3
|
+
SELECTION_META,
|
|
4
|
+
type SelectionBuilder,
|
|
5
|
+
type SelectionMeta,
|
|
6
|
+
type SelectionFieldMeta,
|
|
7
|
+
type FluentFragment,
|
|
8
|
+
type FluentDefiner,
|
|
9
|
+
type HasManyOptions,
|
|
10
|
+
type InferSelection,
|
|
11
|
+
type ScalarMethod,
|
|
12
|
+
type HasOneMethod,
|
|
13
|
+
type HasManyMethod,
|
|
14
|
+
} from './types.js'
|
|
15
|
+
|
|
16
|
+
// Query types for typed filter/orderBy
|
|
17
|
+
export {
|
|
18
|
+
type EntityWhere,
|
|
19
|
+
type EntityOrderBy,
|
|
20
|
+
type ScalarCondition,
|
|
21
|
+
type OrderDirection,
|
|
22
|
+
type TypedHasManyOptions,
|
|
23
|
+
type AliasOptions,
|
|
24
|
+
} from './queryTypes.js'
|
|
25
|
+
|
|
26
|
+
// Runtime
|
|
27
|
+
export { createSelectionBuilder, getSelectionMeta } from './createSelectionBuilder.js'
|
|
28
|
+
export { createFragment } from './createFragment.js'
|
|
29
|
+
export { buildQueryFromSelection, collectPaths, type QuerySpec, type QueryFieldSpec } from './buildQuery.js'
|
|
30
|
+
export { SelectionMetaCollector, mergeSelections, createEmptySelection } from './SelectionMetaCollector.js'
|
|
31
|
+
export { SelectionScope, type HasManyParams } from './SelectionScope.js'
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed query parameter types for filter, orderBy, etc.
|
|
3
|
+
*
|
|
4
|
+
* Uses Input.Condition and Input.OrderDirection from @contember/schema
|
|
5
|
+
* for scalar conditions, wrapped in entity-aware types that work with
|
|
6
|
+
* plain TypeScript interfaces.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Input } from '@contember/schema'
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Re-export Contember types
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Condition operators for scalar values - re-exported from @contember/schema
|
|
17
|
+
*/
|
|
18
|
+
export type ScalarCondition<T> = Input.Condition<T>
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Order direction enum values - string literal union from Contember enum
|
|
22
|
+
*/
|
|
23
|
+
export type OrderDirection = `${Input.OrderDirection}`
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Where Types (for filtering)
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Base composed where with AND/OR/NOT logic
|
|
31
|
+
*/
|
|
32
|
+
export interface ComposedWhere<TEntity> {
|
|
33
|
+
readonly and?: readonly EntityWhere<TEntity>[]
|
|
34
|
+
readonly or?: readonly EntityWhere<TEntity>[]
|
|
35
|
+
readonly not?: EntityWhere<TEntity>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Checks if a type is a "plain object" (entity) vs a scalar like Date
|
|
40
|
+
* Date and other built-in objects are not treated as relations
|
|
41
|
+
*/
|
|
42
|
+
type IsPlainObject<T> =
|
|
43
|
+
T extends Date ? false :
|
|
44
|
+
T extends Array<any> ? false :
|
|
45
|
+
T extends Function ? false :
|
|
46
|
+
T extends object ? true :
|
|
47
|
+
false
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Field-level where clause for an entity
|
|
51
|
+
* Maps each field to its appropriate condition type using Input.Condition
|
|
52
|
+
*/
|
|
53
|
+
export type FieldsWhere<TEntity> = {
|
|
54
|
+
readonly [K in keyof TEntity]?:
|
|
55
|
+
NonNullable<TEntity[K]> extends Array<infer U>
|
|
56
|
+
? IsPlainObject<U> extends true
|
|
57
|
+
? EntityWhere<U> | null // has-many: filter on related items (array of entities)
|
|
58
|
+
: Input.Condition<NonNullable<TEntity[K]>> | null // scalar array: use Input.Condition on the array (supports hasSome, contains, etc.)
|
|
59
|
+
: IsPlainObject<NonNullable<TEntity[K]>> extends true
|
|
60
|
+
? EntityWhere<NonNullable<TEntity[K]>> | null // has-one: filter on related entity
|
|
61
|
+
: Input.Condition<NonNullable<TEntity[K]>> | null // scalar: use Input.Condition
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Full where type for an entity - combines composed and field conditions
|
|
66
|
+
*/
|
|
67
|
+
export type EntityWhere<TEntity> = ComposedWhere<TEntity> & FieldsWhere<TEntity>
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// OrderBy Types (for sorting)
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Order by clause for an entity
|
|
75
|
+
* Each scalar field can have a direction, relations can have nested ordering
|
|
76
|
+
*/
|
|
77
|
+
export type EntityOrderBy<TEntity> = {
|
|
78
|
+
readonly [K in keyof TEntity]?:
|
|
79
|
+
NonNullable<TEntity[K]> extends Array<any>
|
|
80
|
+
? never // has-many cannot be ordered by directly
|
|
81
|
+
: IsPlainObject<NonNullable<TEntity[K]>> extends true
|
|
82
|
+
? EntityOrderBy<NonNullable<TEntity[K]>> | null // has-one: nested ordering
|
|
83
|
+
: OrderDirection | null // scalar: direction (including Date)
|
|
84
|
+
} & {
|
|
85
|
+
readonly _random?: boolean
|
|
86
|
+
readonly _randomSeeded?: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// HasMany Options with Typed Parameters
|
|
91
|
+
// ============================================================================
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Options for has-many relation selection with typed filter/orderBy
|
|
95
|
+
*
|
|
96
|
+
* @typeParam TItem - The related entity type (array item type)
|
|
97
|
+
* @typeParam TAlias - Optional alias for the field
|
|
98
|
+
*/
|
|
99
|
+
export interface TypedHasManyOptions<
|
|
100
|
+
TItem,
|
|
101
|
+
TAlias extends string = string,
|
|
102
|
+
> {
|
|
103
|
+
readonly as?: TAlias
|
|
104
|
+
readonly filter?: EntityWhere<TItem>
|
|
105
|
+
readonly orderBy?: readonly EntityOrderBy<TItem>[]
|
|
106
|
+
readonly limit?: number
|
|
107
|
+
readonly offset?: number
|
|
108
|
+
readonly totalCount?: boolean
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Alias-only options (for scalar or has-one with alias)
|
|
113
|
+
*/
|
|
114
|
+
export interface AliasOptions<TAlias extends string = string> {
|
|
115
|
+
readonly as?: TAlias
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Type Utilities
|
|
120
|
+
// ============================================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Extracts the item type from an array type
|
|
124
|
+
*/
|
|
125
|
+
export type ArrayItemType<T> = T extends Array<infer U> ? U : never
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Checks if a type is an array
|
|
129
|
+
*/
|
|
130
|
+
export type IsArray<T> = T extends Array<any> ? true : false
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extracts non-nullable type
|
|
134
|
+
*/
|
|
135
|
+
export type NonNullableType<T> = Exclude<T, null | undefined>
|