@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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) Microsoft Corporation.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @microsoft/rayfin-data
2
+
3
+ ## Security
4
+
5
+ Microsoft takes the security of our software products and services seriously, which
6
+ includes all source code repositories in our GitHub organizations.
7
+
8
+ **Please do not report security vulnerabilities through public GitHub issues.**
9
+
10
+ For security reporting information, locations, contact information, and policies,
11
+ please review the latest guidance for Microsoft repositories at
12
+ [https://aka.ms/SECURITY.md](https://aka.ms/SECURITY.md).
13
+
14
+ ## Trademarks
15
+
16
+ This project may contain trademarks or logos for projects, products, or services.
17
+ Authorized use of Microsoft trademarks or logos must follow the [Microsoft Trademark and Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general).
18
+ Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
19
+ Any use of third-party trademarks or logos is subject to those third parties' policies.
20
+
21
+ ## License
22
+
23
+ Copyright (c) Microsoft Corporation.
24
+
25
+ MIT License
26
+
27
+ Permission is hereby granted, free of charge, to any person obtaining a copy
28
+ of this software and associated documentation files (the "Software"), to deal
29
+ in the Software without restriction, including without limitation the rights
30
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31
+ copies of the Software, and to permit persons to whom the Software is
32
+ furnished to do so, subject to the following conditions:
33
+
34
+ The above copyright notice and this permission notice shall be included in all
35
+ copies or substantial portions of the Software.
36
+
37
+ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
40
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
43
+ SOFTWARE.
@@ -0,0 +1,64 @@
1
+ import type { ApiClient as ApiClientType } from '@microsoft/rayfin-lib';
2
+ import { GraphQLEntityClient } from '../graphql/GraphQLEntityClient';
3
+ import type { EntitySchema } from '../graphql/types';
4
+ /**
5
+ * Type-safe data clients based on entity schema.
6
+ */
7
+ export type TypedDataClients<TSchema extends EntitySchema> = {
8
+ [K in keyof TSchema]: GraphQLEntityClient<TSchema, K>;
9
+ };
10
+ /**
11
+ * Data-specific operations client for interacting with Data API Builder GraphQL endpoints.
12
+ *
13
+ * Use the `createDataApi` factory function to create instances instead of instantiating directly.
14
+ *
15
+ * @template TSchema - Object type mapping entity names to their types
16
+ */
17
+ export declare class DataApi<TSchema extends EntitySchema = Record<string, any>> {
18
+ private apiClient;
19
+ private clientCache;
20
+ constructor(apiClient: ApiClientType);
21
+ /**
22
+ * Get a GraphQL client for a specific entity.
23
+ */
24
+ getClient<K extends keyof TSchema>(entityName: K): GraphQLEntityClient<TSchema, K>;
25
+ /**
26
+ * Get all available entity names.
27
+ * In a Proxy-based client, this returns all entity names that have been accessed.
28
+ */
29
+ getEntityNames(): (keyof TSchema)[];
30
+ /**
31
+ * Create a Proxy that generates GraphQL clients on-demand.
32
+ */
33
+ private createProxy;
34
+ }
35
+ /**
36
+ * Create a type-safe DataApi instance for interacting with Data API Builder GraphQL endpoints.
37
+ *
38
+ * @template TSchema - Object type mapping entity names to their types
39
+ * @param apiClient - The ApiClient instance for making HTTP requests
40
+ * @returns A fully typed DataApi instance with direct entity access
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const dataApi = createDataApi<{
45
+ * User: User;
46
+ * Organization: Organization;
47
+ * }>(apiClient);
48
+ *
49
+ * // GraphQL fluent interface (direct entity access):
50
+ * const activeUsers = await dataApi.User
51
+ * .select(['id', 'name', 'email'])
52
+ * .where({ isActive: { eq: true } })
53
+ * .orderBy({ createdAt: 'desc' })
54
+ * .execute();
55
+ *
56
+ * // GraphQL mutations:
57
+ * const newUser = await dataApi.User.create({
58
+ * email: 'user@example.com',
59
+ * name: 'Jane Doe'
60
+ * });
61
+ * ```
62
+ */
63
+ export declare function createDataApi<TSchema extends EntitySchema>(apiClient: ApiClientType): DataApi<TSchema> & TypedDataClients<TSchema>;
64
+ //# sourceMappingURL=DataApi.d.ts.map
@@ -0,0 +1,109 @@
1
+ import { GraphQLClient } from '../graphql/GraphQLClient';
2
+ import { GraphQLEntityClient } from '../graphql/GraphQLEntityClient';
3
+ /**
4
+ * Data-specific operations client for interacting with Data API Builder GraphQL endpoints.
5
+ *
6
+ * Use the `createDataApi` factory function to create instances instead of instantiating directly.
7
+ *
8
+ * @template TSchema - Object type mapping entity names to their types
9
+ */
10
+ export class DataApi {
11
+ apiClient;
12
+ clientCache = new Map();
13
+ // Single constructor - requires ApiClient injection
14
+ constructor(apiClient) {
15
+ this.apiClient = apiClient;
16
+ // Return a Proxy that generates GraphQL clients on-demand
17
+ return this.createProxy();
18
+ }
19
+ /**
20
+ * Get a GraphQL client for a specific entity.
21
+ */
22
+ getClient(entityName) {
23
+ const name = String(entityName);
24
+ if (!this.clientCache.has(name)) {
25
+ const graphqlClient = new GraphQLClient(this.apiClient, `/graphql`);
26
+ this.clientCache.set(name, new GraphQLEntityClient(graphqlClient, entityName));
27
+ }
28
+ return this.clientCache.get(name);
29
+ }
30
+ /**
31
+ * Get all available entity names.
32
+ * In a Proxy-based client, this returns all entity names that have been accessed.
33
+ */
34
+ getEntityNames() {
35
+ return Array.from(this.clientCache.keys());
36
+ }
37
+ /**
38
+ * Create a Proxy that generates GraphQL clients on-demand.
39
+ */
40
+ createProxy() {
41
+ return new Proxy(this, {
42
+ get: (target, prop) => {
43
+ // Allow access to class methods and properties
44
+ if (prop in target) {
45
+ return target[prop];
46
+ }
47
+ // Generate entity client for string properties
48
+ if (typeof prop === 'string') {
49
+ return this.getClient(prop);
50
+ }
51
+ return undefined;
52
+ },
53
+ ownKeys: () => {
54
+ // Return only entity names for Object.keys() etc.
55
+ return Array.from(this.clientCache.keys());
56
+ },
57
+ has: (target, prop) => {
58
+ // Check class properties first
59
+ if (prop in target) {
60
+ return true;
61
+ }
62
+ // Always return true for string properties (entity names)
63
+ return typeof prop === 'string';
64
+ },
65
+ getOwnPropertyDescriptor: (target, prop) => {
66
+ if (typeof prop === 'string') {
67
+ return {
68
+ enumerable: true,
69
+ configurable: true,
70
+ value: this.getClient(prop),
71
+ };
72
+ }
73
+ return undefined;
74
+ },
75
+ });
76
+ }
77
+ }
78
+ /**
79
+ * Create a type-safe DataApi instance for interacting with Data API Builder GraphQL endpoints.
80
+ *
81
+ * @template TSchema - Object type mapping entity names to their types
82
+ * @param apiClient - The ApiClient instance for making HTTP requests
83
+ * @returns A fully typed DataApi instance with direct entity access
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const dataApi = createDataApi<{
88
+ * User: User;
89
+ * Organization: Organization;
90
+ * }>(apiClient);
91
+ *
92
+ * // GraphQL fluent interface (direct entity access):
93
+ * const activeUsers = await dataApi.User
94
+ * .select(['id', 'name', 'email'])
95
+ * .where({ isActive: { eq: true } })
96
+ * .orderBy({ createdAt: 'desc' })
97
+ * .execute();
98
+ *
99
+ * // GraphQL mutations:
100
+ * const newUser = await dataApi.User.create({
101
+ * email: 'user@example.com',
102
+ * name: 'Jane Doe'
103
+ * });
104
+ * ```
105
+ */
106
+ export function createDataApi(apiClient) {
107
+ return new DataApi(apiClient);
108
+ }
109
+ //# sourceMappingURL=DataApi.js.map
@@ -0,0 +1,45 @@
1
+ import type { ApiClient } from '@microsoft/rayfin-lib';
2
+ /**
3
+ * GraphQL request payload.
4
+ */
5
+ export interface GraphQLRequest {
6
+ query: string;
7
+ variables?: Record<string, any>;
8
+ operationName?: string;
9
+ }
10
+ /**
11
+ * GraphQL response payload.
12
+ */
13
+ export interface GraphQLResponse<T = any> {
14
+ data?: T;
15
+ errors?: Array<{
16
+ message: string;
17
+ locations?: Array<{
18
+ line: number;
19
+ column: number;
20
+ }>;
21
+ path?: Array<string | number>;
22
+ extensions?: Record<string, any>;
23
+ }>;
24
+ }
25
+ /**
26
+ * GraphQL client for executing queries and mutations.
27
+ */
28
+ export declare class GraphQLClient {
29
+ private apiClient;
30
+ private endpoint;
31
+ constructor(apiClient: ApiClient, endpoint?: string);
32
+ /**
33
+ * Execute a GraphQL query or mutation.
34
+ */
35
+ request<T = any>(query: string, variables?: Record<string, any>, operationName?: string): Promise<T>;
36
+ /**
37
+ * Execute a GraphQL query.
38
+ */
39
+ query<T = any>(query: string, variables?: Record<string, any>): Promise<T>;
40
+ /**
41
+ * Execute a GraphQL mutation.
42
+ */
43
+ mutation<T = any>(mutation: string, variables?: Record<string, any>): Promise<T>;
44
+ }
45
+ //# sourceMappingURL=GraphQLClient.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * GraphQL client for executing queries and mutations.
3
+ */
4
+ export class GraphQLClient {
5
+ apiClient;
6
+ endpoint;
7
+ constructor(apiClient, endpoint = '/graphql') {
8
+ this.apiClient = apiClient;
9
+ this.endpoint = endpoint;
10
+ }
11
+ /**
12
+ * Execute a GraphQL query or mutation.
13
+ */
14
+ async request(query, variables, operationName) {
15
+ const payload = {
16
+ query,
17
+ variables,
18
+ operationName,
19
+ };
20
+ const response = await this.apiClient.post(this.endpoint, payload);
21
+ if (response.errors && response.errors.length > 0) {
22
+ const errorMessage = response.errors
23
+ .map((err) => err.message)
24
+ .join(', ');
25
+ throw new Error(`GraphQL errors: ${errorMessage}`);
26
+ }
27
+ if (response.data === undefined) {
28
+ throw new Error('GraphQL response contains no data');
29
+ }
30
+ return response.data;
31
+ }
32
+ /**
33
+ * Execute a GraphQL query.
34
+ */
35
+ async query(query, variables) {
36
+ return this.request(query, variables);
37
+ }
38
+ /**
39
+ * Execute a GraphQL mutation.
40
+ */
41
+ async mutation(mutation, variables) {
42
+ return this.request(mutation, variables);
43
+ }
44
+ }
45
+ //# sourceMappingURL=GraphQLClient.js.map
@@ -0,0 +1,48 @@
1
+ import type { GraphQLClient } from './GraphQLClient';
2
+ import { GraphQLQueryBuilder } from './GraphQLQueryBuilder';
3
+ import type { EntitySchema, CreateInput, UpdateInput, WhereUniqueInput, FilterInput, OrderByInput, FieldSelection } from './types';
4
+ export declare class GraphQLEntityClient<TSchema extends EntitySchema, TEntity extends keyof TSchema> {
5
+ private graphqlClient;
6
+ private entityNameString;
7
+ constructor(graphqlClient: GraphQLClient, entityName: TEntity);
8
+ private lowercaseFirstLetter;
9
+ select<TFields extends FieldSelection<TSchema[TEntity]>>(fields: TFields): GraphQLQueryBuilder<TSchema, TEntity>;
10
+ where(conditions: FilterInput<TSchema[TEntity]>): GraphQLQueryBuilder<TSchema, TEntity>;
11
+ orderBy(order: OrderByInput<TSchema[TEntity]>): GraphQLQueryBuilder<TSchema, TEntity>;
12
+ first(count: number): GraphQLQueryBuilder<TSchema, TEntity>;
13
+ findMany(filter?: FilterInput<TSchema[TEntity]>): Promise<TSchema[TEntity][]>;
14
+ findFirst(filter?: FilterInput<TSchema[TEntity]>): Promise<TSchema[TEntity] | null>;
15
+ findById(id: string): Promise<TSchema[TEntity] | null>;
16
+ create(input: CreateInput<TSchema[TEntity]>): Promise<TSchema[TEntity]>;
17
+ update(where: WhereUniqueInput<TSchema[TEntity]>, data: UpdateInput<TSchema[TEntity]>): Promise<TSchema[TEntity]>;
18
+ delete(where: WhereUniqueInput<TSchema[TEntity]>): Promise<TSchema[TEntity]>;
19
+ upsert(where: WhereUniqueInput<TSchema[TEntity]>, create: CreateInput<TSchema[TEntity]>, update: UpdateInput<TSchema[TEntity]>): Promise<TSchema[TEntity]>;
20
+ protected buildCreateMutationString(input: CreateInput<TSchema[TEntity]>): string;
21
+ protected buildUpdateMutationString(where: WhereUniqueInput<TSchema[TEntity]>, data: UpdateInput<TSchema[TEntity]>): string;
22
+ protected buildDeleteMutationString(where: WhereUniqueInput<TSchema[TEntity]>): string;
23
+ protected getDefaultFields(data: any): string;
24
+ protected formatMutationInput(data: any): string;
25
+ /**
26
+ * Checks if a value represents a relationship object (has 'id' property)
27
+ */
28
+ private isRelationshipObject;
29
+ /**
30
+ * Extracts the ID value from a relationship object, handling both 'id' and 'Id' casing
31
+ */
32
+ private getRelationshipId;
33
+ /**
34
+ * Checks if a value is a primitive field (not a complex object)
35
+ */
36
+ private isPrimitiveField;
37
+ private formatValue;
38
+ protected extractId(where: WhereUniqueInput<TSchema[TEntity]>): string;
39
+ private extractMutationResult;
40
+ /**
41
+ * Transforms flat FK fields (e.g. category_id) in mutation responses back into
42
+ * nested relationship objects (e.g. category: { id: "..." }) based on the
43
+ * relationship keys present in the original input.
44
+ */
45
+ private nestRelationshipFields;
46
+ private isNotFoundError;
47
+ }
48
+ //# sourceMappingURL=GraphQLEntityClient.d.ts.map
@@ -0,0 +1,277 @@
1
+ import { getPrimaryKeyField } from '@microsoft/rayfin-core';
2
+ import { deserializeDabResponse } from '../utils/serialization';
3
+ import { GraphQLQueryBuilder } from './GraphQLQueryBuilder';
4
+ export class GraphQLEntityClient {
5
+ graphqlClient;
6
+ entityNameString;
7
+ constructor(graphqlClient, entityName) {
8
+ this.graphqlClient = graphqlClient;
9
+ this.entityNameString = String(entityName);
10
+ }
11
+ // Helper method to lowercase only the first letter of the entity name
12
+ lowercaseFirstLetter(str) {
13
+ if (!str)
14
+ return str;
15
+ return str.charAt(0).toLowerCase() + str.slice(1);
16
+ }
17
+ // === QUERY METHODS (return builders for fluent interface) ===
18
+ select(fields) {
19
+ return new GraphQLQueryBuilder(this.graphqlClient, this.entityNameString).select(fields);
20
+ }
21
+ where(conditions) {
22
+ return new GraphQLQueryBuilder(this.graphqlClient, this.entityNameString).where(conditions);
23
+ }
24
+ orderBy(order) {
25
+ return new GraphQLQueryBuilder(this.graphqlClient, this.entityNameString).orderBy(order);
26
+ }
27
+ first(count) {
28
+ return new GraphQLQueryBuilder(this.graphqlClient, this.entityNameString).first(count);
29
+ }
30
+ // === DIRECT QUERY METHODS ===
31
+ async findMany(filter) {
32
+ const builder = new GraphQLQueryBuilder(this.graphqlClient, this.entityNameString);
33
+ if (filter) {
34
+ builder.where(filter);
35
+ }
36
+ return builder.execute();
37
+ }
38
+ async findFirst(filter) {
39
+ const builder = new GraphQLQueryBuilder(this.graphqlClient, this.entityNameString).first(1);
40
+ if (filter) {
41
+ builder.where(filter);
42
+ }
43
+ const results = await builder.execute();
44
+ return results[0] || null;
45
+ }
46
+ async findById(id) {
47
+ const queryName = `${this.lowercaseFirstLetter(this.entityNameString)}_by_pk`;
48
+ const query = `
49
+ query {
50
+ ${queryName}(${getPrimaryKeyField()}: "${id}") {
51
+ ${getPrimaryKeyField()}
52
+ }
53
+ }
54
+ `;
55
+ try {
56
+ const result = await this.graphqlClient.query(query);
57
+ const entityData = result.data?.[queryName] || result[queryName] || null;
58
+ return entityData
59
+ ? deserializeDabResponse(entityData)
60
+ : null;
61
+ }
62
+ catch (error) {
63
+ if (this.isNotFoundError(error)) {
64
+ return null;
65
+ }
66
+ throw error;
67
+ }
68
+ }
69
+ // === MUTATION METHODS ===
70
+ async create(input) {
71
+ const mutationName = `create${this.entityNameString}`;
72
+ const mutation = this.buildCreateMutationString(input);
73
+ const result = await this.graphqlClient.mutation(mutation);
74
+ const entityData = this.extractMutationResult(result, mutationName);
75
+ const deserialized = deserializeDabResponse(entityData);
76
+ return this.nestRelationshipFields(deserialized, input);
77
+ }
78
+ async update(where, data) {
79
+ const mutationName = `update${this.entityNameString}`;
80
+ const mutation = this.buildUpdateMutationString(where, data);
81
+ const result = await this.graphqlClient.mutation(mutation);
82
+ const entityData = this.extractMutationResult(result, mutationName);
83
+ const deserialized = deserializeDabResponse(entityData);
84
+ return this.nestRelationshipFields(deserialized, data);
85
+ }
86
+ async delete(where) {
87
+ const mutationName = `delete${this.entityNameString}`;
88
+ const mutation = this.buildDeleteMutationString(where);
89
+ const result = await this.graphqlClient.mutation(mutation);
90
+ const entityData = this.extractMutationResult(result, mutationName);
91
+ return deserializeDabResponse(entityData);
92
+ }
93
+ async upsert(where, create, update) {
94
+ const id = this.extractId(where);
95
+ const existing = await this.findById(id);
96
+ if (existing) {
97
+ return await this.update(where, update);
98
+ }
99
+ return await this.create(create);
100
+ }
101
+ // === PROTECTED MUTATION BUILDING METHODS (for testing access) ===
102
+ buildCreateMutationString(input) {
103
+ const mutationName = `create${this.entityNameString}`;
104
+ const fieldsToReturn = this.getDefaultFields(input);
105
+ return `
106
+ mutation {
107
+ ${mutationName}(item: ${this.formatMutationInput(input)}) {
108
+ ${fieldsToReturn}
109
+ }
110
+ }
111
+ `.trim();
112
+ }
113
+ buildUpdateMutationString(where, data) {
114
+ const mutationName = `update${this.entityNameString}`;
115
+ const id = this.extractId(where);
116
+ const fieldsToReturn = this.getDefaultFields(data);
117
+ return `
118
+ mutation {
119
+ ${mutationName}(
120
+ ${getPrimaryKeyField()}: "${id}",
121
+ item: ${this.formatMutationInput(data)}
122
+ ) {
123
+ ${fieldsToReturn}
124
+ }
125
+ }
126
+ `.trim();
127
+ }
128
+ buildDeleteMutationString(where) {
129
+ const mutationName = `delete${this.entityNameString}`;
130
+ const id = this.extractId(where);
131
+ return `
132
+ mutation {
133
+ ${mutationName}(${getPrimaryKeyField()}: "${id}") {
134
+ ${getPrimaryKeyField()}
135
+ }
136
+ }
137
+ `.trim();
138
+ }
139
+ // === PROTECTED HELPER METHODS (for testing access) ===
140
+ getDefaultFields(data) {
141
+ const fields = [];
142
+ // Process all entries with unified logic
143
+ Object.entries(data).forEach(([key, value]) => {
144
+ // Skip undefined values
145
+ if (value === undefined) {
146
+ return;
147
+ }
148
+ // Handle relationship objects by adding foreign key fields
149
+ if (this.isRelationshipObject(value)) {
150
+ const foreignKeyField = `${key}_${getPrimaryKeyField()}`;
151
+ if (!fields.includes(foreignKeyField)) {
152
+ fields.push(foreignKeyField);
153
+ }
154
+ }
155
+ // Handle primitive fields (exclude other object types)
156
+ else if (this.isPrimitiveField(value)) {
157
+ fields.push(key);
158
+ }
159
+ });
160
+ // Ensure the PK field is always included and at the beginning
161
+ if (!fields.includes(getPrimaryKeyField())) {
162
+ fields.unshift(getPrimaryKeyField());
163
+ }
164
+ return fields.join('\n ');
165
+ }
166
+ formatMutationInput(data) {
167
+ const entries = [];
168
+ // Process all entries with unified logic
169
+ Object.entries(data).forEach(([key, value]) => {
170
+ // Skip undefined values
171
+ if (value === undefined) {
172
+ return;
173
+ }
174
+ // Handle relationship objects by converting to foreign key
175
+ if (this.isRelationshipObject(value)) {
176
+ const foreignKeyField = `${key}_${getPrimaryKeyField()}`;
177
+ const foreignKeyValue = this.formatValue(this.getRelationshipId(value));
178
+ entries.push(`${foreignKeyField}: ${foreignKeyValue}`);
179
+ }
180
+ // Handle primitive fields (exclude other object types)
181
+ else if (this.isPrimitiveField(value)) {
182
+ entries.push(`${key}: ${this.formatValue(value)}`);
183
+ }
184
+ });
185
+ return `{ ${entries.join(', ')} }`;
186
+ }
187
+ /**
188
+ * Checks if a value represents a relationship object (has 'id' property)
189
+ */
190
+ isRelationshipObject(value) {
191
+ return (typeof value === 'object' &&
192
+ value !== null &&
193
+ !Array.isArray(value) &&
194
+ !(value instanceof Date) &&
195
+ ('id' in value || 'Id' in value));
196
+ }
197
+ /**
198
+ * Extracts the ID value from a relationship object, handling both 'id' and 'Id' casing
199
+ */
200
+ getRelationshipId(value) {
201
+ if ('id' in value) {
202
+ return value.id;
203
+ }
204
+ if ('Id' in value) {
205
+ return value.Id;
206
+ }
207
+ throw new Error('No ID field found in relationship object');
208
+ }
209
+ /**
210
+ * Checks if a value is a primitive field (not a complex object)
211
+ */
212
+ isPrimitiveField(value) {
213
+ return !(typeof value === 'object' &&
214
+ value !== null &&
215
+ !Array.isArray(value) &&
216
+ !(value instanceof Date));
217
+ }
218
+ // === PRIVATE HELPER METHODS ===
219
+ formatValue(value) {
220
+ if (typeof value === 'string') {
221
+ return `"${value
222
+ .replace(/\\/g, '\\\\')
223
+ .replace(/"/g, '\\"')
224
+ .replace(/\n/g, '\\n')
225
+ .replace(/\r/g, '\\r')
226
+ .replace(/\t/g, '\\t')}"`;
227
+ }
228
+ if (typeof value === 'boolean') {
229
+ return String(value);
230
+ }
231
+ if (typeof value === 'number') {
232
+ return String(value);
233
+ }
234
+ if (value instanceof Date) {
235
+ return `"${value.toISOString()}"`;
236
+ }
237
+ if (value === null || value === undefined) {
238
+ return 'null';
239
+ }
240
+ return `"${String(value)}"`;
241
+ }
242
+ extractId(where) {
243
+ return where[getPrimaryKeyField()];
244
+ }
245
+ extractMutationResult(result, operationName) {
246
+ if (result?.data?.[operationName]) {
247
+ return result.data[operationName];
248
+ }
249
+ if (result[operationName]) {
250
+ return result[operationName];
251
+ }
252
+ throw new Error(`Failed to extract result from ${operationName} mutation`);
253
+ }
254
+ /**
255
+ * Transforms flat FK fields (e.g. category_id) in mutation responses back into
256
+ * nested relationship objects (e.g. category: { id: "..." }) based on the
257
+ * relationship keys present in the original input.
258
+ */
259
+ nestRelationshipFields(responseData, inputData) {
260
+ const result = { ...responseData };
261
+ Object.entries(inputData).forEach(([key, value]) => {
262
+ if (value !== undefined && this.isRelationshipObject(value)) {
263
+ const foreignKeyField = `${key}_${getPrimaryKeyField()}`;
264
+ if (foreignKeyField in result) {
265
+ result[key] = { [getPrimaryKeyField()]: result[foreignKeyField] };
266
+ }
267
+ }
268
+ });
269
+ return result;
270
+ }
271
+ isNotFoundError(error) {
272
+ return (error?.message?.includes('not found') ||
273
+ error?.message?.includes('404') ||
274
+ error?.extensions?.code === 'NOT_FOUND');
275
+ }
276
+ }
277
+ //# sourceMappingURL=GraphQLEntityClient.js.map