@microsoft/rayfin-data 1.20.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.
@@ -0,0 +1,45 @@
1
+ import type { GraphQLClient } from './GraphQLClient';
2
+ import type { EntitySchema, FilterInput, OrderByInput, PagedResult, FieldSelection } from './types';
3
+ /**
4
+ * Fluent GraphQL query builder for constructing and executing complex queries.
5
+ *
6
+ * @template TSchema The entity schema type
7
+ * @template TEntity The specific entity name
8
+ */
9
+ export declare class GraphQLQueryBuilder<TSchema extends EntitySchema, TEntity extends keyof TSchema> {
10
+ private graphqlClient;
11
+ private selections;
12
+ private whereConditions;
13
+ private orderByConditions;
14
+ private paginationConfig;
15
+ private readonly entityPluralName;
16
+ constructor(graphqlClient: GraphQLClient, entityName: string);
17
+ private lowercaseFirstLetter;
18
+ select<TFields extends FieldSelection<TSchema[TEntity]>>(fields: TFields): this;
19
+ where(conditions: FilterInput<TSchema[TEntity]>): this;
20
+ orderBy(order: OrderByInput<TSchema[TEntity]>): this;
21
+ first(count: number): this;
22
+ after(cursor: string): this;
23
+ execute(): Promise<TSchema[TEntity][]>;
24
+ executePaginated(): Promise<PagedResult<TSchema[TEntity]>>;
25
+ findFirst(): Promise<TSchema[TEntity] | null>;
26
+ protected buildQuery(): string;
27
+ protected buildQueryWithPagination(): string;
28
+ private buildFieldSelection;
29
+ /**
30
+ * DAB represents collection navigation properties as "Connection" objects containing an `items` array.
31
+ * For ergonomics (and to match our schema types like `todos?: Todo[]`), unwrap any selected nested
32
+ * connection objects to their `items` arrays.
33
+ */
34
+ private unwrapNestedConnectionItems;
35
+ private buildArguments;
36
+ private buildFilterObject;
37
+ private buildFieldFilter;
38
+ private isRelationshipNullFilter;
39
+ private isNullFilterObject;
40
+ private hasNestedSelectionFor;
41
+ private buildOrderByArray;
42
+ private formatValue;
43
+ private mergeFilters;
44
+ }
45
+ //# sourceMappingURL=GraphQLQueryBuilder.d.ts.map
@@ -0,0 +1,325 @@
1
+ import { getPrimaryKeyField } from '@microsoft/rayfin-core';
2
+ import { EntityNameResolver } from '@microsoft/rayfin-lib';
3
+ import { ResponseHandler } from './ResponseHandler';
4
+ /**
5
+ * Fluent GraphQL query builder for constructing and executing complex queries.
6
+ *
7
+ * @template TSchema The entity schema type
8
+ * @template TEntity The specific entity name
9
+ */
10
+ export class GraphQLQueryBuilder {
11
+ graphqlClient;
12
+ selections = [];
13
+ whereConditions = {};
14
+ orderByConditions = [];
15
+ paginationConfig = {};
16
+ entityPluralName;
17
+ constructor(graphqlClient, entityName) {
18
+ this.graphqlClient = graphqlClient;
19
+ // Use EntityNameResolver for proper pluralization and convert to lowercase for DAB compliance
20
+ const pluralName = EntityNameResolver.getPlural(entityName);
21
+ this.entityPluralName = this.lowercaseFirstLetter(pluralName);
22
+ }
23
+ // Helper method to lowercase only the first letter of the entity name
24
+ lowercaseFirstLetter(str) {
25
+ if (!str)
26
+ return str;
27
+ return str.charAt(0).toLowerCase() + str.slice(1);
28
+ }
29
+ // === FLUENT BUILDER METHODS ===
30
+ select(fields) {
31
+ this.selections = [...fields];
32
+ return this;
33
+ }
34
+ where(conditions) {
35
+ this.whereConditions = this.mergeFilters(this.whereConditions, conditions);
36
+ return this;
37
+ }
38
+ orderBy(order) {
39
+ this.orderByConditions.push(order);
40
+ return this;
41
+ }
42
+ first(count) {
43
+ this.paginationConfig.first = count;
44
+ return this;
45
+ }
46
+ after(cursor) {
47
+ this.paginationConfig.after = cursor;
48
+ return this;
49
+ }
50
+ // === EXECUTION METHODS ===
51
+ async execute() {
52
+ const query = this.buildQuery();
53
+ const result = await this.graphqlClient.query(query);
54
+ const unwrapped = ResponseHandler.unwrapGraphQLResponse(result, this.entityPluralName, false);
55
+ const items = Array.isArray(unwrapped) ? unwrapped : unwrapped.items;
56
+ return this.unwrapNestedConnectionItems(items);
57
+ }
58
+ async executePaginated() {
59
+ const query = this.buildQueryWithPagination();
60
+ const result = await this.graphqlClient.query(query);
61
+ const unwrapped = ResponseHandler.unwrapGraphQLResponse(result, this.entityPluralName, true);
62
+ const paged = unwrapped;
63
+ return {
64
+ ...paged,
65
+ items: this.unwrapNestedConnectionItems(paged.items),
66
+ };
67
+ }
68
+ async findFirst() {
69
+ const originalFirst = this.paginationConfig.first;
70
+ this.paginationConfig.first = 1;
71
+ try {
72
+ const results = await this.execute();
73
+ return results[0] || null;
74
+ }
75
+ finally {
76
+ this.paginationConfig.first = originalFirst;
77
+ }
78
+ }
79
+ // === PROTECTED QUERY BUILDING METHODS (for testing access) ===
80
+ buildQuery() {
81
+ const fields = this.buildFieldSelection();
82
+ const args = this.buildArguments();
83
+ return `
84
+ query {
85
+ ${this.entityPluralName}${args} {
86
+ items {
87
+ ${fields}
88
+ }
89
+ }
90
+ }
91
+ `.trim();
92
+ }
93
+ buildQueryWithPagination() {
94
+ const fields = this.buildFieldSelection();
95
+ const args = this.buildArguments();
96
+ return `
97
+ query {
98
+ ${this.entityPluralName}${args} {
99
+ items {
100
+ ${fields}
101
+ }
102
+ endCursor
103
+ hasNextPage
104
+ }
105
+ }
106
+ `.trim();
107
+ }
108
+ buildFieldSelection() {
109
+ if (this.selections.length === 0) {
110
+ return 'id';
111
+ }
112
+ // Group fields by their root and nested parts
113
+ const fieldMap = new Map();
114
+ this.selections.forEach((field) => {
115
+ const fieldStr = String(field);
116
+ if (fieldStr.includes('.')) {
117
+ const [root, nested] = fieldStr.split('.', 2);
118
+ if (!fieldMap.has(root)) {
119
+ fieldMap.set(root, new Set());
120
+ }
121
+ fieldMap.get(root).add(nested);
122
+ }
123
+ else {
124
+ fieldMap.set(fieldStr, new Set());
125
+ }
126
+ });
127
+ // Build GraphQL field selection
128
+ return Array.from(fieldMap.entries())
129
+ .map(([field, nestedFields]) => {
130
+ if (nestedFields.size === 0) {
131
+ return field;
132
+ }
133
+ else {
134
+ const nestedSelection = Array.from(nestedFields).join('\n ');
135
+ // DAB GraphQL represents collections (e.g. one-to-many) as *Connection objects
136
+ // with an `items` field. Since our selection DSL uses `posts.id` / `todos.Title`
137
+ // for array relationships, we need to shape the GraphQL selection accordingly.
138
+ //
139
+ // Use EntityNameResolver to properly detect plural fields instead of crude string suffix check
140
+ if (EntityNameResolver.isPlural(field)) {
141
+ return `${field} {\n items {\n ${nestedSelection}\n }\n }`;
142
+ }
143
+ return `${field} {\n ${nestedSelection}\n }`;
144
+ }
145
+ })
146
+ .join('\n ');
147
+ }
148
+ /**
149
+ * DAB represents collection navigation properties as "Connection" objects containing an `items` array.
150
+ * For ergonomics (and to match our schema types like `todos?: Todo[]`), unwrap any selected nested
151
+ * connection objects to their `items` arrays.
152
+ */
153
+ unwrapNestedConnectionItems(entities) {
154
+ if (!entities.length || this.selections.length === 0) {
155
+ return entities;
156
+ }
157
+ const nestedRoots = new Set(this.selections
158
+ .map((field) => String(field))
159
+ .filter((field) => field.includes('.'))
160
+ .map((field) => field.split('.', 2)[0]));
161
+ if (nestedRoots.size === 0) {
162
+ return entities;
163
+ }
164
+ for (const entity of entities) {
165
+ for (const root of nestedRoots) {
166
+ const value = entity?.[root];
167
+ if (value &&
168
+ typeof value === 'object' &&
169
+ !Array.isArray(value) &&
170
+ Array.isArray(value.items)) {
171
+ entity[root] = value.items;
172
+ }
173
+ }
174
+ }
175
+ return entities;
176
+ }
177
+ buildArguments(excludePagination = false) {
178
+ const args = [];
179
+ // Filter argument (DAB object format)
180
+ if (Object.keys(this.whereConditions).length > 0) {
181
+ const filterObj = this.buildFilterObject(this.whereConditions);
182
+ args.push(`filter: ${filterObj}`);
183
+ }
184
+ // OrderBy argument
185
+ if (this.orderByConditions.length > 0) {
186
+ const orderByArray = this.buildOrderByArray();
187
+ args.push(`orderBy: ${orderByArray}`);
188
+ }
189
+ if (!excludePagination) {
190
+ // Pagination arguments (DAB only supports forward pagination)
191
+ if (this.paginationConfig.first !== undefined) {
192
+ args.push(`first: ${this.paginationConfig.first}`);
193
+ }
194
+ if (this.paginationConfig.after !== undefined) {
195
+ args.push(`after: "${this.paginationConfig.after}"`);
196
+ }
197
+ }
198
+ return args.length > 0 ? `(${args.join(', ')})` : '';
199
+ }
200
+ buildFilterObject(filter) {
201
+ const conditions = [];
202
+ for (const [key, value] of Object.entries(filter)) {
203
+ if (key === 'and') {
204
+ const andConditions = value
205
+ .map((cond) => this.buildFilterObject(cond))
206
+ .join(', ');
207
+ conditions.push(`and: [${andConditions}]`);
208
+ }
209
+ else if (key === 'or') {
210
+ const orConditions = value
211
+ .map((cond) => this.buildFilterObject(cond))
212
+ .join(', ');
213
+ conditions.push(`or: [${orConditions}]`);
214
+ }
215
+ else if (key === 'not') {
216
+ const notCondition = this.buildFilterObject(value);
217
+ conditions.push(`not: ${notCondition}`);
218
+ }
219
+ else {
220
+ if (this.isRelationshipNullFilter(key, value)) {
221
+ const foreignKeyField = `${key}_${getPrimaryKeyField()}`;
222
+ const fieldFilter = this.buildFieldFilter(foreignKeyField, value);
223
+ conditions.push(`${foreignKeyField}: ${fieldFilter}`);
224
+ }
225
+ else {
226
+ const fieldFilter = this.buildFieldFilter(key, value);
227
+ conditions.push(`${key}: ${fieldFilter}`);
228
+ }
229
+ }
230
+ }
231
+ return `{ ${conditions.join(', ')} }`;
232
+ }
233
+ buildFieldFilter(fieldName, value) {
234
+ if (typeof value === 'object' &&
235
+ !Array.isArray(value) &&
236
+ !(value instanceof Date)) {
237
+ const operators = Object.entries(value)
238
+ .map(([op, val]) => {
239
+ if (typeof val === 'object' &&
240
+ !Array.isArray(val) &&
241
+ !(val instanceof Date)) {
242
+ return `${op}: ${this.buildFieldFilter(fieldName, val)}`;
243
+ }
244
+ else {
245
+ return `${op}: ${this.formatValue(val)}`;
246
+ }
247
+ })
248
+ .join(', ');
249
+ return `{ ${operators} }`;
250
+ }
251
+ return `{ eq: ${this.formatValue(value)} }`;
252
+ }
253
+ isRelationshipNullFilter(key, value) {
254
+ if (!this.isNullFilterObject(value)) {
255
+ return false;
256
+ }
257
+ if (EntityNameResolver.isPlural(key)) {
258
+ return false;
259
+ }
260
+ return this.hasNestedSelectionFor(key);
261
+ }
262
+ isNullFilterObject(value) {
263
+ if (typeof value !== 'object' ||
264
+ value === null ||
265
+ Array.isArray(value) ||
266
+ value instanceof Date) {
267
+ return false;
268
+ }
269
+ const entries = Object.entries(value);
270
+ return (entries.length === 1 &&
271
+ entries[0][0] === 'isNull' &&
272
+ typeof entries[0][1] === 'boolean');
273
+ }
274
+ hasNestedSelectionFor(key) {
275
+ return this.selections.some((field) => {
276
+ const fieldStr = String(field);
277
+ if (!fieldStr.includes('.')) {
278
+ return false;
279
+ }
280
+ const [root] = fieldStr.split('.', 2);
281
+ return root === key;
282
+ });
283
+ }
284
+ buildOrderByArray() {
285
+ // DAB expects a single OrderBy object, not an array
286
+ // Convert from our array format to single object format
287
+ const orderEntries = [];
288
+ for (const order of this.orderByConditions) {
289
+ for (const [field, direction] of Object.entries(order)) {
290
+ // Use enum values (ASC/DESC) not quoted strings
291
+ const enumValue = direction?.toUpperCase();
292
+ orderEntries.push(`${field}: ${enumValue}`);
293
+ }
294
+ }
295
+ return `{ ${orderEntries.join(', ')} }`;
296
+ }
297
+ formatValue(value) {
298
+ if (typeof value === 'string') {
299
+ return `"${value.replace(/"/g, '\\"')}"`;
300
+ }
301
+ if (typeof value === 'boolean') {
302
+ return String(value);
303
+ }
304
+ if (typeof value === 'number') {
305
+ return String(value);
306
+ }
307
+ if (value instanceof Date) {
308
+ return `"${value.toISOString()}"`;
309
+ }
310
+ if (Array.isArray(value)) {
311
+ return `[${value.map((v) => this.formatValue(v)).join(', ')}]`;
312
+ }
313
+ if (value === null) {
314
+ return 'null';
315
+ }
316
+ return `"${String(value)}"`;
317
+ }
318
+ mergeFilters(existing, additional) {
319
+ if (Object.keys(existing).length === 0) {
320
+ return additional;
321
+ }
322
+ return { and: [existing, additional] };
323
+ }
324
+ }
325
+ //# sourceMappingURL=GraphQLQueryBuilder.js.map
@@ -0,0 +1,18 @@
1
+ import type { PagedResult } from './types';
2
+ export declare class ResponseError extends Error {
3
+ readonly context: {
4
+ response?: any;
5
+ expectedStructure?: string;
6
+ };
7
+ constructor(message: string, context?: {
8
+ response?: any;
9
+ expectedStructure?: string;
10
+ });
11
+ }
12
+ export declare class ResponseHandler {
13
+ /**
14
+ * Unwrap DAB GraphQL response structure
15
+ */
16
+ static unwrapGraphQLResponse<T>(response: any, entityPluralName: string, expectPagination?: boolean): T[] | PagedResult<T>;
17
+ }
18
+ //# sourceMappingURL=ResponseHandler.d.ts.map
@@ -0,0 +1,66 @@
1
+ import { deserializeDabResponse } from '../utils/serialization';
2
+ export class ResponseError extends Error {
3
+ context;
4
+ constructor(message, context = {}) {
5
+ super(message);
6
+ this.context = context;
7
+ this.name = 'ResponseError';
8
+ }
9
+ }
10
+ export class ResponseHandler {
11
+ /**
12
+ * Unwrap DAB GraphQL response structure
13
+ */
14
+ static unwrapGraphQLResponse(response, entityPluralName, expectPagination = false) {
15
+ // Handle case where response is already the data portion
16
+ let entityData;
17
+ if (response?.data) {
18
+ // Full GraphQL response format: { data: { entityName: { items: [...] } } }
19
+ entityData = response.data[entityPluralName];
20
+ }
21
+ else if (response?.[entityPluralName]) {
22
+ // Data portion only: { entityName: { items: [...] } }
23
+ entityData = response[entityPluralName];
24
+ }
25
+ else {
26
+ throw new ResponseError('Invalid response structure: missing data field', {
27
+ response,
28
+ expectedStructure: '{ data: { [entityName]: { items: [...] } } } or { [entityName]: { items: [...] } }',
29
+ });
30
+ }
31
+ if (!entityData) {
32
+ throw new ResponseError(`No data found for entity: ${entityPluralName}`, {
33
+ response,
34
+ expectedStructure: `{ data: { ${entityPluralName}: { items: [...] } } }`,
35
+ });
36
+ }
37
+ // For single item queries (e.g., findById)
38
+ if (!entityData.items && !Array.isArray(entityData)) {
39
+ return deserializeDabResponse(entityData);
40
+ }
41
+ // Validate DAB structure for list queries
42
+ if (!Array.isArray(entityData.items)) {
43
+ throw new ResponseError('Invalid DAB response: items is not an array', {
44
+ response: entityData,
45
+ expectedStructure: '{ items: [...], endCursor?: string, hasNextPage?: boolean }',
46
+ });
47
+ }
48
+ if (expectPagination) {
49
+ // DAB returns pagination fields at the top level of the entity response
50
+ const paginatedResult = {
51
+ items: entityData.items,
52
+ // DAB only supports forward pagination (hasNextPage, endCursor)
53
+ hasNextPage: entityData.hasNextPage ?? false,
54
+ endCursor: entityData.endCursor ?? undefined,
55
+ };
56
+ // Apply DAB deserialization to the items
57
+ return {
58
+ ...paginatedResult,
59
+ items: deserializeDabResponse(paginatedResult.items),
60
+ };
61
+ }
62
+ // Apply DAB deserialization to the items array
63
+ return deserializeDabResponse(entityData.items);
64
+ }
65
+ }
66
+ //# sourceMappingURL=ResponseHandler.js.map
@@ -0,0 +1,211 @@
1
+ import type { PrimaryKeyField } from '@microsoft/rayfin-core';
2
+ /**
3
+ * Base entity schema type for the client.
4
+ */
5
+ export type EntitySchema = Record<string, any>;
6
+ /**
7
+ * Clean entity keys that exclude prototype methods and functions
8
+ */
9
+ export type CleanEntityKeys<T> = keyof {
10
+ [K in keyof T as T[K] extends Function ? never : K]: T[K];
11
+ };
12
+ /**
13
+ * Nested field path support with type constraints for relationships
14
+ */
15
+ export type NestedFieldPath<T> = T extends object ? {
16
+ [K in CleanEntityKeys<T>]: K extends string ? NonNullable<T[K]> extends (infer U)[] ? U extends object ? `${K}.${CleanEntityKeys<U>}` | K : K : NonNullable<T[K]> extends object ? `${K}.${CleanEntityKeys<NonNullable<T[K]>>}` | K : K : never;
17
+ }[CleanEntityKeys<T>] : never;
18
+ /**
19
+ * Type-safe field selection with nested paths
20
+ */
21
+ export type FieldSelection<T> = readonly (CleanEntityKeys<T> | NestedFieldPath<T>)[];
22
+ /**
23
+ * DAB-compliant filter input
24
+ */
25
+ export type RelationshipIsNullFilter<T> = T extends object ? T extends readonly unknown[] ? never : {
26
+ isNull?: boolean;
27
+ } : never;
28
+ /**
29
+ * Filter value that can be a direct value, a filter input, or a relationship isNull filter
30
+ */
31
+ export type FilterValue<T, K extends keyof T> = T[K] | FilterInput<NonNullable<T[K]>> | FieldFilterInput<NonNullable<T[K]>> | (undefined extends T[K] ? RelationshipIsNullFilter<NonNullable<T[K]>> : never);
32
+ /**
33
+ * DAB-compliant filter input type
34
+ */
35
+ export type FilterInput<T> = {
36
+ [K in keyof T]?: FilterValue<T, K>;
37
+ } & {
38
+ and?: FilterInput<T>[];
39
+ or?: FilterInput<T>[];
40
+ };
41
+ /**
42
+ * Field-specific filter operators per DAB specification
43
+ */
44
+ export type FieldFilterInput<T> = T extends string ? StringFilterInput : T extends number ? NumberFilterInput : T extends boolean ? BooleanFilterInput : T extends Date ? DateFilterInput : GenericFilterInput<T>;
45
+ export interface StringFilterInput {
46
+ eq?: string;
47
+ neq?: string;
48
+ gt?: string;
49
+ gte?: string;
50
+ lt?: string;
51
+ lte?: string;
52
+ contains?: string;
53
+ notContains?: string;
54
+ startsWith?: string;
55
+ endsWith?: string;
56
+ isNull?: boolean;
57
+ in?: string[];
58
+ }
59
+ export interface NumberFilterInput {
60
+ eq?: number;
61
+ neq?: number;
62
+ gt?: number;
63
+ gte?: number;
64
+ lt?: number;
65
+ lte?: number;
66
+ isNull?: boolean;
67
+ in?: number[];
68
+ }
69
+ export interface BooleanFilterInput {
70
+ eq?: boolean;
71
+ neq?: boolean;
72
+ isNull?: boolean;
73
+ in?: boolean[];
74
+ }
75
+ /**
76
+ * WARNING: DAB currently does not support comparison operators for Date fields in PostgreSQL
77
+ */
78
+ export interface DateFilterInput {
79
+ eq?: Date;
80
+ neq?: Date;
81
+ gt?: Date;
82
+ gte?: Date;
83
+ lt?: Date;
84
+ lte?: Date;
85
+ isNull?: boolean;
86
+ in?: Date[];
87
+ }
88
+ export interface GenericFilterInput<T> {
89
+ eq?: Partial<T>;
90
+ neq?: Partial<T>;
91
+ }
92
+ /**
93
+ * DAB pagination configuration (forward pagination only).
94
+ *
95
+ * Microsoft Data API Builder only supports forward pagination with `first` and `after`.
96
+ * Backward pagination with `last` and `before` is not supported.
97
+ *
98
+ * @see https://learn.microsoft.com/en-us/azure/data-api-builder/keywords/after-graphql
99
+ * @see https://learn.microsoft.com/en-us/azure/data-api-builder/keywords/first-graphql
100
+ */
101
+ export interface PaginationConfig {
102
+ /** Number of items to return per page */
103
+ first?: number;
104
+ /** Cursor to resume pagination from */
105
+ after?: string;
106
+ }
107
+ /**
108
+ * DAB response wrapper
109
+ */
110
+ export interface PagedResult<T> {
111
+ items: T[];
112
+ hasNextPage: boolean;
113
+ endCursor?: string;
114
+ totalCount?: number;
115
+ }
116
+ /**
117
+ * DAB order by input
118
+ */
119
+ export type OrderByInput<T> = {
120
+ [K in keyof T]?: 'asc' | 'desc';
121
+ };
122
+ /**
123
+ * Extracts the primary key field from an entity using the centralized {@link PrimaryKeyField} type.
124
+ *
125
+ * @see {@link RelationshipInput} for usage in mutation input types
126
+ */
127
+ export type PrimaryKeyOnly<T> = PrimaryKeyField extends keyof T ? Pick<T, PrimaryKeyField> : never;
128
+ /**
129
+ * Flexible input type for relationship fields in mutations.
130
+ *
131
+ * Accepts either:
132
+ * - Full entity object (current behavior, useful when you have the object)
133
+ * - Object with only the primary key field(s) (new ergonomic shorthand)
134
+ *
135
+ * @remarks
136
+ * The runtime already handles both forms via `isRelationshipObject()` which
137
+ * detects objects with 'id' or 'Id' properties and extracts the ID value
138
+ * to form the foreign key (e.g., `category_id`).
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * // Option 1: Pass full object (current behavior)
143
+ * await client.data.Todo.create({
144
+ * title: 'My Todo',
145
+ * category: { id: 'cat-123', name: 'Work', color: '#ff0000' },
146
+ * });
147
+ *
148
+ * // Option 2: Pass primary-key-only object (new ergonomic shorthand)
149
+ * await client.data.Todo.create({
150
+ * title: 'My Todo',
151
+ * category: { id: 'cat-123' },
152
+ * });
153
+ * ```
154
+ */
155
+ export type RelationshipInput<T> = T | PrimaryKeyOnly<T>;
156
+ /**
157
+ * Detects if a type is a relationship (object with a primary key that's not a primitive wrapper).
158
+ *
159
+ * @remarks
160
+ * A relationship is detected as an object type that has a {@link PrimaryKeyField} property,
161
+ * but is not a Date or Array (which are object types but not relationships).
162
+ */
163
+ export type IsRelationship<T> = PrimaryKeyField extends keyof T ? T extends Date | Array<any> ? false : true : false;
164
+ /**
165
+ * Transforms an entity type for mutation input.
166
+ *
167
+ * - `@one` relationship fields become `RelationshipInput<T>` (accepts full object or id-only)
168
+ * - `@many` relationship fields (arrays) are allowed but ignored at runtime
169
+ * (they're managed via the FK on the "many" side, not on the parent)
170
+ * - Primitive fields remain unchanged
171
+ *
172
+ * @remarks
173
+ * This type preserves optionality: if a field is optional in the entity (e.g., `category?: Category`),
174
+ * it remains optional in the mutation input.
175
+ *
176
+ * `@many` arrays can be passed for convenience but will be ignored by the GraphQL mutation.
177
+ * To manage `@many` relationships, update the child entities' `@one` references instead.
178
+ */
179
+ export type MutationInput<T> = {
180
+ [K in keyof T]: NonNullable<T[K]> extends Array<any> ? T[K] : IsRelationship<NonNullable<T[K]>> extends true ? RelationshipInput<NonNullable<T[K]>> | (undefined extends T[K] ? undefined : never) : T[K];
181
+ };
182
+ /**
183
+ * Input type for creating new entities.
184
+ * The primary key field is optional since it's typically database-generated,
185
+ * but can be provided if the user wants to specify a particular id.
186
+ */
187
+ export type CreateInput<T> = PrimaryKeyField extends keyof T ? Omit<MutationInput<T>, PrimaryKeyField> & Partial<Pick<T, PrimaryKeyField>> : MutationInput<T>;
188
+ /**
189
+ * Input type for updating existing entities.
190
+ *
191
+ * All fields are optional. Relationship fields accept full object or primary-key-only.
192
+ * `@many` relationship arrays are allowed but ignored at runtime.
193
+ *
194
+ * @example
195
+ * ```typescript
196
+ * // Update with primary-key-only for relationship
197
+ * const input: UpdateInput<Todo> = {
198
+ * title: 'Updated Title',
199
+ * category: { id: 'new-cat-id' },
200
+ * };
201
+ * ```
202
+ */
203
+ export type UpdateInput<T> = Partial<MutationInput<T>>;
204
+ /**
205
+ * Input type for uniquely identifying an entity.
206
+ * Uses the centralized PrimaryKeyField type alias.
207
+ */
208
+ export type WhereUniqueInput<T> = {
209
+ [K in PrimaryKeyField]: string;
210
+ };
211
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,9 @@
1
+ export { createDataApi, DataApi } from './client/DataApi';
2
+ export type { TypedDataClients } from './client/DataApi';
3
+ export { GraphQLClient } from './graphql/GraphQLClient';
4
+ export { GraphQLEntityClient } from './graphql/GraphQLEntityClient';
5
+ export { GraphQLQueryBuilder } from './graphql/GraphQLQueryBuilder';
6
+ export { ResponseHandler } from './graphql/ResponseHandler';
7
+ export type { GraphQLRequest, GraphQLResponse } from './graphql/GraphQLClient';
8
+ export type { EntitySchema, CleanEntityKeys, NestedFieldPath, IsRelationship, RelationshipInput, PrimaryKeyOnly, MutationInput, CreateInput, UpdateInput, WhereUniqueInput, FilterInput, FilterValue, RelationshipIsNullFilter, FieldFilterInput, FieldSelection, OrderByInput, PaginationConfig, PagedResult, StringFilterInput, NumberFilterInput, BooleanFilterInput, DateFilterInput, GenericFilterInput, } from './graphql/types';
9
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // Main exports
2
+ export { createDataApi, DataApi } from './client/DataApi';
3
+ export { GraphQLClient } from './graphql/GraphQLClient';
4
+ export { GraphQLEntityClient } from './graphql/GraphQLEntityClient';
5
+ export { GraphQLQueryBuilder } from './graphql/GraphQLQueryBuilder';
6
+ // DAB utilities
7
+ export { ResponseHandler } from './graphql/ResponseHandler';
8
+ //# sourceMappingURL=index.js.map