@contember/bindx 0.1.4 → 0.1.8

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.
Files changed (76) hide show
  1. package/dist/adapter/ContemberAdapter.d.ts +11 -14
  2. package/dist/adapter/ContemberAdapter.d.ts.map +1 -1
  3. package/dist/brand/ComponentBrand.d.ts +1 -24
  4. package/dist/brand/ComponentBrand.d.ts.map +1 -1
  5. package/dist/contember/index.d.ts +1 -2
  6. package/dist/contember/index.d.ts.map +1 -1
  7. package/dist/handles/EntityHandle.d.ts +18 -29
  8. package/dist/handles/EntityHandle.d.ts.map +1 -1
  9. package/dist/handles/FieldHandle.d.ts +5 -3
  10. package/dist/handles/FieldHandle.d.ts.map +1 -1
  11. package/dist/handles/HasManyListHandle.d.ts +4 -3
  12. package/dist/handles/HasManyListHandle.d.ts.map +1 -1
  13. package/dist/handles/HasOneHandle.d.ts +33 -36
  14. package/dist/handles/HasOneHandle.d.ts.map +1 -1
  15. package/dist/handles/PlaceholderHandle.d.ts +9 -19
  16. package/dist/handles/PlaceholderHandle.d.ts.map +1 -1
  17. package/dist/handles/proxyFactory.d.ts +9 -37
  18. package/dist/handles/proxyFactory.d.ts.map +1 -1
  19. package/dist/handles/types.d.ts +4 -0
  20. package/dist/handles/types.d.ts.map +1 -1
  21. package/dist/index.d.ts +3 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/persistence/ContemberSchemaMutationAdapter.d.ts +24 -1
  24. package/dist/persistence/ContemberSchemaMutationAdapter.d.ts.map +1 -1
  25. package/dist/schema/ContemberSchema.d.ts +1 -97
  26. package/dist/schema/ContemberSchema.d.ts.map +1 -1
  27. package/dist/schema/SchemaLoader.d.ts +1 -27
  28. package/dist/schema/SchemaLoader.d.ts.map +1 -1
  29. package/dist/schema/SchemaRegistry.d.ts +1 -94
  30. package/dist/schema/SchemaRegistry.d.ts.map +1 -1
  31. package/dist/schema/roles.d.ts +1 -61
  32. package/dist/schema/roles.d.ts.map +1 -1
  33. package/dist/schema/types.d.ts +1 -178
  34. package/dist/schema/types.d.ts.map +1 -1
  35. package/dist/selection/SelectionMetaCollector.d.ts +1 -31
  36. package/dist/selection/SelectionMetaCollector.d.ts.map +1 -1
  37. package/dist/selection/SelectionScope.d.ts +1 -76
  38. package/dist/selection/SelectionScope.d.ts.map +1 -1
  39. package/dist/selection/buildQuery.d.ts +1 -44
  40. package/dist/selection/buildQuery.d.ts.map +1 -1
  41. package/dist/selection/createFragment.d.ts +1 -28
  42. package/dist/selection/createFragment.d.ts.map +1 -1
  43. package/dist/selection/createSelectionBuilder.d.ts +1 -17
  44. package/dist/selection/createSelectionBuilder.d.ts.map +1 -1
  45. package/dist/selection/queryTypes.d.ts +1 -83
  46. package/dist/selection/queryTypes.d.ts.map +1 -1
  47. package/dist/selection/types.d.ts +1 -237
  48. package/dist/selection/types.d.ts.map +1 -1
  49. package/dist/utils/aliasGenerator.d.ts +1 -24
  50. package/dist/utils/aliasGenerator.d.ts.map +1 -1
  51. package/package.json +4 -4
  52. package/src/adapter/ContemberAdapter.ts +193 -123
  53. package/src/brand/ComponentBrand.ts +1 -28
  54. package/src/contember/index.ts +1 -8
  55. package/src/handles/EntityHandle.ts +131 -61
  56. package/src/handles/FieldHandle.ts +19 -3
  57. package/src/handles/HasManyListHandle.ts +15 -12
  58. package/src/handles/HasOneHandle.ts +98 -66
  59. package/src/handles/PlaceholderHandle.ts +26 -24
  60. package/src/handles/proxyFactory.ts +29 -128
  61. package/src/handles/types.ts +6 -0
  62. package/src/index.ts +22 -3
  63. package/src/persistence/ContemberSchemaMutationAdapter.ts +19 -1
  64. package/src/schema/ContemberSchema.ts +14 -132
  65. package/src/schema/SchemaLoader.ts +1 -93
  66. package/src/schema/SchemaRegistry.ts +1 -286
  67. package/src/schema/roles.ts +10 -83
  68. package/src/schema/types.ts +21 -238
  69. package/src/selection/SelectionMetaCollector.ts +1 -122
  70. package/src/selection/SelectionScope.ts +1 -208
  71. package/src/selection/buildQuery.ts +1 -134
  72. package/src/selection/createFragment.ts +1 -50
  73. package/src/selection/createSelectionBuilder.ts +1 -283
  74. package/src/selection/queryTypes.ts +8 -135
  75. package/src/selection/types.ts +13 -320
  76. package/src/utils/aliasGenerator.ts +1 -56
@@ -1,134 +1 @@
1
- import type { SelectionMeta, SelectionFieldMeta } from './types.js'
2
-
3
- /**
4
- * Query specification for a single field
5
- */
6
- export interface QueryFieldSpec {
7
- /** Name of the field as it appears in the output */
8
- name: string
9
- /** Path to the field in the source entity */
10
- sourcePath: string[]
11
- /** For nested objects/entities */
12
- nested?: QuerySpec
13
- /** Whether this is an array/collection */
14
- isArray?: boolean
15
- /** For has-many: filter parameters */
16
- filter?: unknown
17
- /** For has-many: ordering parameters */
18
- orderBy?: unknown
19
- /** For has-many: limit */
20
- limit?: number
21
- /** For has-many: offset */
22
- offset?: number
23
- /** For has-many: request totalCount via paginateRelation */
24
- totalCount?: boolean
25
- }
26
-
27
- /**
28
- * Query specification for fetching data.
29
- * Backend adapters use this to construct their queries.
30
- */
31
- export interface QuerySpec {
32
- /** List of fields to fetch */
33
- fields: QueryFieldSpec[]
34
- }
35
-
36
- /**
37
- * Builds a QuerySpec from SelectionMeta.
38
- * This is what gets passed to the backend adapter.
39
- *
40
- * @param meta - The selection metadata
41
- */
42
- export function buildQueryFromSelection(meta: SelectionMeta): QuerySpec {
43
- const fields: QueryFieldSpec[] = []
44
-
45
- // Always include 'id' field if not already selected
46
- // This is needed for entity identity and HasOneAccessor to detect if relation exists
47
- if (!meta.fields.has('id')) {
48
- fields.push({
49
- name: 'id',
50
- sourcePath: ['id'],
51
- })
52
- }
53
-
54
- for (const [_alias, fieldMeta] of meta.fields) {
55
- // Skip relation fields where no nested fields were selected.
56
- // This occurs when a relation handle is passed as a prop (e.g., HasManyDataGrid's `field` prop)
57
- // without any of its fields being read during JSX collection. The consuming component
58
- // is expected to handle its own data fetching for that relation.
59
- if (fieldMeta.isRelation && fieldMeta.nested && fieldMeta.nested.fields.size === 0) {
60
- continue
61
- }
62
- fields.push(buildFieldSpecFromSelection(fieldMeta))
63
- }
64
-
65
- return { fields }
66
- }
67
-
68
- /**
69
- * Builds a QueryFieldSpec from SelectionFieldMeta
70
- */
71
- function buildFieldSpecFromSelection(meta: SelectionFieldMeta): QueryFieldSpec {
72
- const spec: QueryFieldSpec = {
73
- name: meta.alias,
74
- sourcePath: [meta.fieldName],
75
- }
76
-
77
- if (meta.isArray) {
78
- spec.isArray = true
79
-
80
- // Add has-many parameters
81
- if (meta.hasManyParams) {
82
- if (meta.hasManyParams.filter !== undefined) {
83
- spec.filter = meta.hasManyParams.filter
84
- }
85
- if (meta.hasManyParams.orderBy !== undefined) {
86
- spec.orderBy = meta.hasManyParams.orderBy
87
- }
88
- if (meta.hasManyParams.limit !== undefined) {
89
- spec.limit = meta.hasManyParams.limit
90
- }
91
- if (meta.hasManyParams.offset !== undefined) {
92
- spec.offset = meta.hasManyParams.offset
93
- }
94
- if (meta.hasManyParams.totalCount) {
95
- spec.totalCount = true
96
- }
97
- }
98
-
99
- if (meta.nested) {
100
- spec.nested = buildQueryFromSelection(meta.nested)
101
- }
102
- } else if (meta.nested) {
103
- spec.nested = buildQueryFromSelection(meta.nested)
104
- }
105
-
106
- return spec
107
- }
108
-
109
- /**
110
- * Collects all unique paths that need to be fetched.
111
- * Useful for debugging and optimization.
112
- */
113
- export function collectPaths(meta: SelectionMeta, basePath: string[] = []): string[][] {
114
- const paths: string[][] = []
115
-
116
- for (const [_alias, fieldMeta] of meta.fields) {
117
- const fullPath = [...basePath, fieldMeta.fieldName]
118
-
119
- if (fieldMeta.isArray && fieldMeta.nested) {
120
- // For arrays, collect paths from nested metadata
121
- const nestedPaths = collectPaths(fieldMeta.nested, fullPath)
122
- paths.push(...nestedPaths)
123
- } else if (fieldMeta.nested) {
124
- // For nested objects, collect paths recursively
125
- const nestedPaths = collectPaths(fieldMeta.nested, fullPath)
126
- paths.push(...nestedPaths)
127
- } else {
128
- // Scalar field
129
- paths.push(fullPath)
130
- }
131
- }
132
-
133
- return paths
134
- }
1
+ export { buildQueryFromSelection, collectPaths, type QuerySpec, type QueryFieldSpec } from '@contember/bindx-client'
@@ -1,50 +1 @@
1
- import { createSelectionBuilder } from './createSelectionBuilder.js'
2
- import { SELECTION_META, type FluentFragment, type SelectionBuilder } from './types.js'
3
-
4
- /**
5
- * Creates a reusable fragment using the fluent builder API.
6
- *
7
- * This is a curried function - first call provides the model type,
8
- * second call provides the definer function.
9
- *
10
- * @example
11
- * ```ts
12
- * // Define a fragment
13
- * const AuthorFragment = createFragment<Author>()(e =>
14
- * e.id().name().email()
15
- * )
16
- *
17
- * // Use in useEntity
18
- * const article = useEntity('Article', { id }, e =>
19
- * e.title().author(AuthorFragment)
20
- * )
21
- *
22
- * // Or compose fragments
23
- * const ArticleFragment = createFragment<Article>()(e =>
24
- * e.id().title()
25
- * .author(AuthorFragment)
26
- * .tags(TagFragment)
27
- * )
28
- * ```
29
- */
30
- export function createFragment<TModel>() {
31
- return function <TResult extends object>(
32
- definer: (builder: SelectionBuilder<TModel>) => SelectionBuilder<TModel, TResult, object>,
33
- ): FluentFragment<TModel, TResult> {
34
- // Create initial builder
35
- const builder = createSelectionBuilder<TModel>()
36
-
37
- // Execute definer to get final builder with accumulated selections
38
- const resultBuilder = definer(builder)
39
-
40
- // Extract metadata
41
- const meta = resultBuilder[SELECTION_META]
42
-
43
- return {
44
- __meta: meta,
45
- __resultType: undefined as unknown as TResult,
46
- __modelType: undefined as unknown as TModel,
47
- __isFragment: true,
48
- }
49
- }
50
- }
1
+ export { createFragment } from '@contember/bindx-client'
@@ -1,283 +1 @@
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
- }
1
+ export { createSelectionBuilder, getSelectionMeta } from '@contember/bindx-client'
@@ -1,135 +1,8 @@
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>
1
+ export {
2
+ type EntityWhere,
3
+ type EntityOrderBy,
4
+ type ScalarCondition,
5
+ type OrderDirection,
6
+ type TypedHasManyOptions,
7
+ type AliasOptions,
8
+ } from '@contember/bindx-client'