@constructive-io/graphql-query 3.2.4 → 3.3.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.
Files changed (88) hide show
  1. package/README.md +411 -65
  2. package/ast.d.ts +4 -4
  3. package/ast.js +24 -9
  4. package/client/error.d.ts +95 -0
  5. package/client/error.js +277 -0
  6. package/client/execute.d.ts +57 -0
  7. package/client/execute.js +124 -0
  8. package/client/index.d.ts +8 -0
  9. package/client/index.js +20 -0
  10. package/client/typed-document.d.ts +31 -0
  11. package/client/typed-document.js +44 -0
  12. package/custom-ast.d.ts +22 -8
  13. package/custom-ast.js +16 -1
  14. package/esm/ast.js +22 -7
  15. package/esm/client/error.js +271 -0
  16. package/esm/client/execute.js +120 -0
  17. package/esm/client/index.js +8 -0
  18. package/esm/client/typed-document.js +40 -0
  19. package/esm/custom-ast.js +16 -1
  20. package/esm/generators/field-selector.js +381 -0
  21. package/esm/generators/index.js +13 -0
  22. package/esm/generators/mutations.js +200 -0
  23. package/esm/generators/naming-helpers.js +154 -0
  24. package/esm/generators/select.js +661 -0
  25. package/esm/index.js +30 -0
  26. package/esm/introspect/index.js +9 -0
  27. package/esm/introspect/infer-tables.js +697 -0
  28. package/esm/introspect/schema-query.js +120 -0
  29. package/esm/introspect/transform-schema.js +271 -0
  30. package/esm/introspect/transform.js +38 -0
  31. package/esm/meta-object/convert.js +3 -0
  32. package/esm/meta-object/format.json +11 -41
  33. package/esm/meta-object/validate.js +20 -4
  34. package/esm/query-builder.js +14 -18
  35. package/esm/types/index.js +18 -0
  36. package/esm/types/introspection.js +54 -0
  37. package/esm/types/mutation.js +4 -0
  38. package/esm/types/query.js +4 -0
  39. package/esm/types/schema.js +5 -0
  40. package/esm/types/selection.js +4 -0
  41. package/esm/utils.js +69 -0
  42. package/generators/field-selector.d.ts +30 -0
  43. package/generators/field-selector.js +387 -0
  44. package/generators/index.d.ts +9 -0
  45. package/generators/index.js +42 -0
  46. package/generators/mutations.d.ts +30 -0
  47. package/generators/mutations.js +238 -0
  48. package/generators/naming-helpers.d.ts +48 -0
  49. package/generators/naming-helpers.js +169 -0
  50. package/generators/select.d.ts +39 -0
  51. package/generators/select.js +705 -0
  52. package/index.d.ts +19 -0
  53. package/index.js +34 -1
  54. package/introspect/index.d.ts +9 -0
  55. package/introspect/index.js +25 -0
  56. package/introspect/infer-tables.d.ts +42 -0
  57. package/introspect/infer-tables.js +700 -0
  58. package/introspect/schema-query.d.ts +20 -0
  59. package/introspect/schema-query.js +123 -0
  60. package/introspect/transform-schema.d.ts +86 -0
  61. package/introspect/transform-schema.js +281 -0
  62. package/introspect/transform.d.ts +20 -0
  63. package/introspect/transform.js +43 -0
  64. package/meta-object/convert.d.ts +3 -0
  65. package/meta-object/convert.js +3 -0
  66. package/meta-object/format.json +11 -41
  67. package/meta-object/validate.d.ts +8 -3
  68. package/meta-object/validate.js +20 -4
  69. package/package.json +4 -3
  70. package/query-builder.d.ts +11 -12
  71. package/query-builder.js +25 -29
  72. package/{types.d.ts → types/core.d.ts} +25 -18
  73. package/types/index.d.ts +12 -0
  74. package/types/index.js +34 -0
  75. package/types/introspection.d.ts +121 -0
  76. package/types/introspection.js +62 -0
  77. package/types/mutation.d.ts +45 -0
  78. package/types/mutation.js +5 -0
  79. package/types/query.d.ts +91 -0
  80. package/types/query.js +5 -0
  81. package/types/schema.d.ts +265 -0
  82. package/types/schema.js +6 -0
  83. package/types/selection.d.ts +43 -0
  84. package/types/selection.js +5 -0
  85. package/utils.d.ts +17 -0
  86. package/utils.js +72 -0
  87. /package/esm/{types.js → types/core.js} +0 -0
  88. /package/{types.js → types/core.js} +0 -0
package/custom-ast.d.ts CHANGED
@@ -1,9 +1,14 @@
1
- import type { CleanField, MetaField } from './types';
2
- export declare function getCustomAst(fieldDefn?: MetaField): any;
1
+ import type { FieldNode } from 'graphql';
2
+ import type { CleanField } from './types/schema';
3
+ import type { MetaField } from './types';
4
+ /**
5
+ * Get custom AST for MetaField type - handles PostgreSQL types that need subfield selections
6
+ */
7
+ export declare function getCustomAst(fieldDefn?: MetaField): FieldNode | null;
3
8
  /**
4
9
  * Generate custom AST for CleanField type - handles GraphQL types that need subfield selections
5
10
  */
6
- export declare function getCustomAstForCleanField(field: CleanField): any;
11
+ export declare function getCustomAstForCleanField(field: CleanField): FieldNode;
7
12
  /**
8
13
  * Check if a CleanField requires subfield selection based on its GraphQL type
9
14
  */
@@ -11,11 +16,20 @@ export declare function requiresSubfieldSelection(field: CleanField): boolean;
11
16
  /**
12
17
  * Generate AST for GeometryPoint type
13
18
  */
14
- export declare function geometryPointAst(name: string): any;
19
+ export declare function geometryPointAst(name: string): FieldNode;
15
20
  /**
16
21
  * Generate AST for GeometryGeometryCollection type
17
22
  */
18
- export declare function geometryCollectionAst(name: string): any;
19
- export declare function geometryAst(name: string): any;
20
- export declare function intervalAst(name: string): any;
21
- export declare function isIntervalType(obj: any): boolean;
23
+ export declare function geometryCollectionAst(name: string): FieldNode;
24
+ /**
25
+ * Generate AST for generic geometry type (returns geojson)
26
+ */
27
+ export declare function geometryAst(name: string): FieldNode;
28
+ /**
29
+ * Generate AST for interval type
30
+ */
31
+ export declare function intervalAst(name: string): FieldNode;
32
+ /**
33
+ * Check if an object has interval type shape
34
+ */
35
+ export declare function isIntervalType(obj: unknown): boolean;
package/custom-ast.js CHANGED
@@ -43,6 +43,9 @@ exports.intervalAst = intervalAst;
43
43
  exports.isIntervalType = isIntervalType;
44
44
  const t = __importStar(require("gql-ast"));
45
45
  const graphql_1 = require("graphql");
46
+ /**
47
+ * Get custom AST for MetaField type - handles PostgreSQL types that need subfield selections
48
+ */
46
49
  function getCustomAst(fieldDefn) {
47
50
  if (!fieldDefn) {
48
51
  return null;
@@ -152,6 +155,7 @@ function geometryCollectionAst(name) {
152
155
  t.field({
153
156
  name: 'geometries',
154
157
  selectionSet: t.selectionSet({
158
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
155
159
  selections: [inlineFragment], // gql-ast limitation with inline fragments
156
160
  }),
157
161
  }),
@@ -159,6 +163,9 @@ function geometryCollectionAst(name) {
159
163
  }),
160
164
  });
161
165
  }
166
+ /**
167
+ * Generate AST for generic geometry type (returns geojson)
168
+ */
162
169
  function geometryAst(name) {
163
170
  return t.field({
164
171
  name,
@@ -167,6 +174,9 @@ function geometryAst(name) {
167
174
  }),
168
175
  });
169
176
  }
177
+ /**
178
+ * Generate AST for interval type
179
+ */
170
180
  function intervalAst(name) {
171
181
  return t.field({
172
182
  name,
@@ -185,6 +195,11 @@ function intervalAst(name) {
185
195
  function toFieldArray(strArr) {
186
196
  return strArr.map((fieldName) => t.field({ name: fieldName }));
187
197
  }
198
+ /**
199
+ * Check if an object has interval type shape
200
+ */
188
201
  function isIntervalType(obj) {
189
- return ['days', 'hours', 'minutes', 'months', 'seconds', 'years'].every((key) => obj.hasOwnProperty(key));
202
+ if (!obj || typeof obj !== 'object')
203
+ return false;
204
+ return ['days', 'hours', 'minutes', 'months', 'seconds', 'years'].every((key) => Object.prototype.hasOwnProperty.call(obj, key));
190
205
  }
package/esm/ast.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as t from 'gql-ast';
2
- import { OperationTypeNode, } from 'graphql';
3
- import { camelize, singularize } from 'inflection';
2
+ import { OperationTypeNode } from 'graphql';
3
+ import { camelize, singularize } from 'inflekt';
4
4
  import { getCustomAst } from './custom-ast';
5
5
  const NON_MUTABLE_PROPS = ['createdAt', 'createdBy', 'updatedAt', 'updatedBy'];
6
6
  const objectToArray = (obj) => Object.keys(obj).map((k) => ({
@@ -48,7 +48,7 @@ const createGqlMutation = ({ operationName, mutationName, selectArgs, selections
48
48
  ],
49
49
  });
50
50
  };
51
- export const getAll = ({ queryName, operationName, query: _query, selection, }) => {
51
+ export const getAll = ({ queryName, operationName, selection, }) => {
52
52
  const selections = getSelections(selection);
53
53
  const opSel = [
54
54
  t.field({
@@ -170,7 +170,10 @@ export const getMany = ({ builder, queryName, operationName, query, selection, }
170
170
  t.argument({ name: 'offset', value: t.variable({ name: 'offset' }) }),
171
171
  t.argument({ name: 'after', value: t.variable({ name: 'after' }) }),
172
172
  t.argument({ name: 'before', value: t.variable({ name: 'before' }) }),
173
- t.argument({ name: 'condition', value: t.variable({ name: 'condition' }) }),
173
+ t.argument({
174
+ name: 'condition',
175
+ value: t.variable({ name: 'condition' }),
176
+ }),
174
177
  t.argument({ name: 'filter', value: t.variable({ name: 'filter' }) }),
175
178
  t.argument({ name: 'orderBy', value: t.variable({ name: 'orderBy' }) }),
176
179
  ];
@@ -419,9 +422,21 @@ export const deleteOne = ({ mutationName, operationName, mutation, }) => {
419
422
  };
420
423
  export function getSelections(selection = []) {
421
424
  const selectionAst = (field) => {
422
- return typeof field === 'string'
423
- ? t.field({ name: field })
424
- : getCustomAst(field.fieldDefn) || t.field({ name: field.name });
425
+ if (typeof field === 'string') {
426
+ return t.field({ name: field });
427
+ }
428
+ // Check if fieldDefn has MetaField shape (has type.pgType)
429
+ const fieldDefn = field.fieldDefn;
430
+ if (fieldDefn &&
431
+ 'type' in fieldDefn &&
432
+ fieldDefn.type &&
433
+ typeof fieldDefn.type === 'object' &&
434
+ 'pgType' in fieldDefn.type) {
435
+ const customAst = getCustomAst(fieldDefn);
436
+ if (customAst)
437
+ return customAst;
438
+ }
439
+ return t.field({ name: field.name });
425
440
  };
426
441
  return selection
427
442
  .map((selectionDefn) => {
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Error handling for GraphQL operations
3
+ * Provides consistent error types and parsing for PostGraphile responses
4
+ */
5
+ // ============================================================================
6
+ // Error Types
7
+ // ============================================================================
8
+ export const DataErrorType = {
9
+ // Network/Connection errors
10
+ NETWORK_ERROR: 'NETWORK_ERROR',
11
+ TIMEOUT_ERROR: 'TIMEOUT_ERROR',
12
+ // Validation errors
13
+ VALIDATION_FAILED: 'VALIDATION_FAILED',
14
+ REQUIRED_FIELD_MISSING: 'REQUIRED_FIELD_MISSING',
15
+ INVALID_MUTATION_DATA: 'INVALID_MUTATION_DATA',
16
+ // Query errors
17
+ QUERY_GENERATION_FAILED: 'QUERY_GENERATION_FAILED',
18
+ QUERY_EXECUTION_FAILED: 'QUERY_EXECUTION_FAILED',
19
+ // Permission errors
20
+ UNAUTHORIZED: 'UNAUTHORIZED',
21
+ FORBIDDEN: 'FORBIDDEN',
22
+ // Schema errors
23
+ TABLE_NOT_FOUND: 'TABLE_NOT_FOUND',
24
+ // Request errors
25
+ BAD_REQUEST: 'BAD_REQUEST',
26
+ NOT_FOUND: 'NOT_FOUND',
27
+ // GraphQL-specific errors
28
+ GRAPHQL_ERROR: 'GRAPHQL_ERROR',
29
+ // PostgreSQL constraint errors (surfaced via PostGraphile)
30
+ UNIQUE_VIOLATION: 'UNIQUE_VIOLATION',
31
+ FOREIGN_KEY_VIOLATION: 'FOREIGN_KEY_VIOLATION',
32
+ NOT_NULL_VIOLATION: 'NOT_NULL_VIOLATION',
33
+ CHECK_VIOLATION: 'CHECK_VIOLATION',
34
+ EXCLUSION_VIOLATION: 'EXCLUSION_VIOLATION',
35
+ // Generic errors
36
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR',
37
+ };
38
+ /**
39
+ * Standard error class for data layer operations
40
+ */
41
+ export class DataError extends Error {
42
+ type;
43
+ code;
44
+ originalError;
45
+ context;
46
+ tableName;
47
+ fieldName;
48
+ constraint;
49
+ constructor(type, message, options = {}) {
50
+ super(message);
51
+ this.name = 'DataError';
52
+ this.type = type;
53
+ this.code = options.code;
54
+ this.originalError = options.originalError;
55
+ this.context = options.context;
56
+ this.tableName = options.tableName;
57
+ this.fieldName = options.fieldName;
58
+ this.constraint = options.constraint;
59
+ if (Error.captureStackTrace) {
60
+ Error.captureStackTrace(this, DataError);
61
+ }
62
+ }
63
+ getUserMessage() {
64
+ switch (this.type) {
65
+ case DataErrorType.NETWORK_ERROR:
66
+ return 'Network error. Please check your connection and try again.';
67
+ case DataErrorType.TIMEOUT_ERROR:
68
+ return 'Request timed out. Please try again.';
69
+ case DataErrorType.UNAUTHORIZED:
70
+ return 'You are not authorized. Please log in and try again.';
71
+ case DataErrorType.FORBIDDEN:
72
+ return 'You do not have permission to access this resource.';
73
+ case DataErrorType.VALIDATION_FAILED:
74
+ return 'Validation failed. Please check your input and try again.';
75
+ case DataErrorType.REQUIRED_FIELD_MISSING:
76
+ return this.fieldName
77
+ ? `The field "${this.fieldName}" is required.`
78
+ : 'A required field is missing.';
79
+ case DataErrorType.UNIQUE_VIOLATION:
80
+ return this.fieldName
81
+ ? `A record with this ${this.fieldName} already exists.`
82
+ : 'A record with this value already exists.';
83
+ case DataErrorType.FOREIGN_KEY_VIOLATION:
84
+ return 'This record references a record that does not exist.';
85
+ case DataErrorType.NOT_NULL_VIOLATION:
86
+ return this.fieldName
87
+ ? `The field "${this.fieldName}" cannot be empty.`
88
+ : 'A required field cannot be empty.';
89
+ case DataErrorType.CHECK_VIOLATION:
90
+ return this.fieldName
91
+ ? `The value for "${this.fieldName}" is not valid.`
92
+ : 'The value does not meet the required constraints.';
93
+ default:
94
+ return this.message || 'An unexpected error occurred.';
95
+ }
96
+ }
97
+ isRetryable() {
98
+ return (this.type === DataErrorType.NETWORK_ERROR ||
99
+ this.type === DataErrorType.TIMEOUT_ERROR);
100
+ }
101
+ }
102
+ // ============================================================================
103
+ // PostgreSQL Error Codes
104
+ // ============================================================================
105
+ export const PG_ERROR_CODES = {
106
+ UNIQUE_VIOLATION: '23505',
107
+ FOREIGN_KEY_VIOLATION: '23503',
108
+ NOT_NULL_VIOLATION: '23502',
109
+ CHECK_VIOLATION: '23514',
110
+ EXCLUSION_VIOLATION: '23P01',
111
+ NUMERIC_VALUE_OUT_OF_RANGE: '22003',
112
+ STRING_DATA_RIGHT_TRUNCATION: '22001',
113
+ INVALID_TEXT_REPRESENTATION: '22P02',
114
+ DATETIME_FIELD_OVERFLOW: '22008',
115
+ UNDEFINED_TABLE: '42P01',
116
+ UNDEFINED_COLUMN: '42703',
117
+ INSUFFICIENT_PRIVILEGE: '42501',
118
+ };
119
+ // ============================================================================
120
+ // Error Factory
121
+ // ============================================================================
122
+ export const createError = {
123
+ network: (originalError) => new DataError(DataErrorType.NETWORK_ERROR, 'Network error occurred', {
124
+ originalError,
125
+ }),
126
+ timeout: (originalError) => new DataError(DataErrorType.TIMEOUT_ERROR, 'Request timed out', {
127
+ originalError,
128
+ }),
129
+ unauthorized: (message = 'Authentication required') => new DataError(DataErrorType.UNAUTHORIZED, message),
130
+ forbidden: (message = 'Access forbidden') => new DataError(DataErrorType.FORBIDDEN, message),
131
+ badRequest: (message, code) => new DataError(DataErrorType.BAD_REQUEST, message, { code }),
132
+ notFound: (message = 'Resource not found') => new DataError(DataErrorType.NOT_FOUND, message),
133
+ graphql: (message, code) => new DataError(DataErrorType.GRAPHQL_ERROR, message, { code }),
134
+ uniqueViolation: (message, fieldName, constraint) => new DataError(DataErrorType.UNIQUE_VIOLATION, message, {
135
+ fieldName,
136
+ constraint,
137
+ code: '23505',
138
+ }),
139
+ foreignKeyViolation: (message, fieldName, constraint) => new DataError(DataErrorType.FOREIGN_KEY_VIOLATION, message, {
140
+ fieldName,
141
+ constraint,
142
+ code: '23503',
143
+ }),
144
+ notNullViolation: (message, fieldName, constraint) => new DataError(DataErrorType.NOT_NULL_VIOLATION, message, {
145
+ fieldName,
146
+ constraint,
147
+ code: '23502',
148
+ }),
149
+ unknown: (originalError) => new DataError(DataErrorType.UNKNOWN_ERROR, originalError.message, {
150
+ originalError,
151
+ }),
152
+ };
153
+ function parseGraphQLErrorCode(code) {
154
+ if (!code)
155
+ return DataErrorType.UNKNOWN_ERROR;
156
+ const normalized = code.toUpperCase();
157
+ // GraphQL standard codes
158
+ if (normalized === 'UNAUTHENTICATED')
159
+ return DataErrorType.UNAUTHORIZED;
160
+ if (normalized === 'FORBIDDEN')
161
+ return DataErrorType.FORBIDDEN;
162
+ if (normalized === 'GRAPHQL_VALIDATION_FAILED')
163
+ return DataErrorType.QUERY_GENERATION_FAILED;
164
+ // PostgreSQL SQLSTATE codes
165
+ if (code === PG_ERROR_CODES.UNIQUE_VIOLATION)
166
+ return DataErrorType.UNIQUE_VIOLATION;
167
+ if (code === PG_ERROR_CODES.FOREIGN_KEY_VIOLATION)
168
+ return DataErrorType.FOREIGN_KEY_VIOLATION;
169
+ if (code === PG_ERROR_CODES.NOT_NULL_VIOLATION)
170
+ return DataErrorType.NOT_NULL_VIOLATION;
171
+ if (code === PG_ERROR_CODES.CHECK_VIOLATION)
172
+ return DataErrorType.CHECK_VIOLATION;
173
+ if (code === PG_ERROR_CODES.EXCLUSION_VIOLATION)
174
+ return DataErrorType.EXCLUSION_VIOLATION;
175
+ return DataErrorType.UNKNOWN_ERROR;
176
+ }
177
+ function classifyByMessage(message) {
178
+ const lower = message.toLowerCase();
179
+ if (lower.includes('timeout') || lower.includes('timed out')) {
180
+ return DataErrorType.TIMEOUT_ERROR;
181
+ }
182
+ if (lower.includes('network') ||
183
+ lower.includes('fetch') ||
184
+ lower.includes('failed to fetch')) {
185
+ return DataErrorType.NETWORK_ERROR;
186
+ }
187
+ if (lower.includes('unauthorized') ||
188
+ lower.includes('authentication required')) {
189
+ return DataErrorType.UNAUTHORIZED;
190
+ }
191
+ if (lower.includes('forbidden') || lower.includes('permission')) {
192
+ return DataErrorType.FORBIDDEN;
193
+ }
194
+ if (lower.includes('duplicate key') || lower.includes('already exists')) {
195
+ return DataErrorType.UNIQUE_VIOLATION;
196
+ }
197
+ if (lower.includes('foreign key constraint')) {
198
+ return DataErrorType.FOREIGN_KEY_VIOLATION;
199
+ }
200
+ if (lower.includes('not-null constraint') ||
201
+ lower.includes('null value in column')) {
202
+ return DataErrorType.NOT_NULL_VIOLATION;
203
+ }
204
+ return DataErrorType.UNKNOWN_ERROR;
205
+ }
206
+ function extractFieldFromError(message, constraint, column) {
207
+ if (column)
208
+ return column;
209
+ const columnMatch = message.match(/column\s+"?([a-z_][a-z0-9_]*)"?/i);
210
+ if (columnMatch)
211
+ return columnMatch[1];
212
+ if (constraint) {
213
+ const constraintMatch = constraint.match(/_([a-z_][a-z0-9_]*)_(?:key|fkey|check|pkey)$/i);
214
+ if (constraintMatch)
215
+ return constraintMatch[1];
216
+ }
217
+ const keyMatch = message.match(/Key\s+\(([a-z_][a-z0-9_]*)\)/i);
218
+ if (keyMatch)
219
+ return keyMatch[1];
220
+ return undefined;
221
+ }
222
+ /**
223
+ * Parse any error into a DataError
224
+ */
225
+ export function parseGraphQLError(error) {
226
+ if (error instanceof DataError) {
227
+ return error;
228
+ }
229
+ // GraphQL error object
230
+ if (error &&
231
+ typeof error === 'object' &&
232
+ 'message' in error &&
233
+ typeof error.message === 'string') {
234
+ const gqlError = error;
235
+ const extCode = gqlError.extensions?.code;
236
+ const mappedType = parseGraphQLErrorCode(extCode);
237
+ const column = gqlError.extensions?.column;
238
+ const constraint = gqlError.extensions?.constraint;
239
+ const fieldName = extractFieldFromError(gqlError.message, constraint, column);
240
+ if (mappedType !== DataErrorType.UNKNOWN_ERROR) {
241
+ return new DataError(mappedType, gqlError.message, {
242
+ code: extCode,
243
+ fieldName,
244
+ constraint,
245
+ context: gqlError.extensions,
246
+ });
247
+ }
248
+ // Fallback: classify by message
249
+ const fallbackType = classifyByMessage(gqlError.message);
250
+ return new DataError(fallbackType, gqlError.message, {
251
+ code: extCode,
252
+ fieldName,
253
+ constraint,
254
+ context: gqlError.extensions,
255
+ });
256
+ }
257
+ // Standard Error
258
+ if (error instanceof Error) {
259
+ const type = classifyByMessage(error.message);
260
+ return new DataError(type, error.message, { originalError: error });
261
+ }
262
+ // Unknown
263
+ const message = typeof error === 'string' ? error : 'Unknown error occurred';
264
+ return new DataError(DataErrorType.UNKNOWN_ERROR, message);
265
+ }
266
+ /**
267
+ * Check if value is a DataError
268
+ */
269
+ export function isDataError(error) {
270
+ return error instanceof DataError;
271
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * GraphQL execution utilities
3
+ */
4
+ import { print } from 'graphql';
5
+ import { createError, parseGraphQLError } from './error';
6
+ import { TypedDocumentString } from './typed-document';
7
+ // ============================================================================
8
+ // Helpers
9
+ // ============================================================================
10
+ function documentToString(document) {
11
+ if (typeof document === 'string')
12
+ return document;
13
+ if (document instanceof TypedDocumentString)
14
+ return document.toString();
15
+ // DocumentNode
16
+ if (document && typeof document === 'object' && 'kind' in document) {
17
+ return print(document);
18
+ }
19
+ throw createError.badRequest('Invalid GraphQL document');
20
+ }
21
+ // ============================================================================
22
+ // Execute Function
23
+ // ============================================================================
24
+ /**
25
+ * Execute a GraphQL operation against an endpoint
26
+ */
27
+ export async function execute(endpoint, document, variables, options = {}) {
28
+ const { headers = {}, timeout = 30000, signal } = options;
29
+ // Create timeout controller
30
+ const controller = new AbortController();
31
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
32
+ // Combine signals if provided
33
+ const combinedSignal = signal
34
+ ? AbortSignal.any([signal, controller.signal])
35
+ : controller.signal;
36
+ try {
37
+ const response = await fetch(endpoint, {
38
+ method: 'POST',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ Accept: 'application/graphql-response+json, application/json',
42
+ ...headers,
43
+ },
44
+ body: JSON.stringify({
45
+ query: documentToString(document),
46
+ ...(variables !== undefined && { variables }),
47
+ }),
48
+ signal: combinedSignal,
49
+ });
50
+ clearTimeout(timeoutId);
51
+ if (!response.ok) {
52
+ throw await handleHttpError(response);
53
+ }
54
+ const result = await response.json();
55
+ if (result.errors?.length) {
56
+ throw parseGraphQLError(result.errors[0]);
57
+ }
58
+ return result.data;
59
+ }
60
+ catch (error) {
61
+ clearTimeout(timeoutId);
62
+ if (error instanceof Error && error.name === 'AbortError') {
63
+ throw createError.timeout();
64
+ }
65
+ if (error instanceof Error && error.message.includes('fetch')) {
66
+ throw createError.network(error);
67
+ }
68
+ throw parseGraphQLError(error);
69
+ }
70
+ }
71
+ async function handleHttpError(response) {
72
+ const { status, statusText } = response;
73
+ if (status === 401) {
74
+ return createError.unauthorized('Authentication required');
75
+ }
76
+ if (status === 403) {
77
+ return createError.forbidden('Access forbidden');
78
+ }
79
+ if (status === 404) {
80
+ return createError.notFound('GraphQL endpoint not found');
81
+ }
82
+ // Try to extract error from response body
83
+ try {
84
+ const body = await response.json();
85
+ if (body.errors?.length) {
86
+ return parseGraphQLError(body.errors[0]);
87
+ }
88
+ if (body.message) {
89
+ return createError.badRequest(body.message);
90
+ }
91
+ }
92
+ catch {
93
+ // Couldn't parse response
94
+ }
95
+ return createError.badRequest(`Request failed: ${status} ${statusText}`);
96
+ }
97
+ /**
98
+ * Create a GraphQL client instance
99
+ */
100
+ export function createGraphQLClient(options) {
101
+ const { endpoint, headers: defaultHeaders = {}, timeout: defaultTimeout = 30000, } = options;
102
+ return {
103
+ /**
104
+ * Execute a GraphQL operation
105
+ */
106
+ async execute(document, variables, options = {}) {
107
+ return execute(endpoint, document, variables, {
108
+ headers: { ...defaultHeaders, ...options.headers },
109
+ timeout: options.timeout ?? defaultTimeout,
110
+ signal: options.signal,
111
+ });
112
+ },
113
+ /**
114
+ * Get the endpoint URL
115
+ */
116
+ getEndpoint() {
117
+ return endpoint;
118
+ },
119
+ };
120
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Client barrel export
3
+ *
4
+ * Re-exports client utilities for GraphQL execution and error handling.
5
+ */
6
+ export { TypedDocumentString, } from './typed-document';
7
+ export { DataError, DataErrorType, PG_ERROR_CODES, createError, parseGraphQLError, isDataError, } from './error';
8
+ export { execute, createGraphQLClient, } from './execute';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * TypedDocumentString - Type-safe wrapper for GraphQL documents
3
+ * Compatible with GraphQL codegen client preset
4
+ */
5
+ /**
6
+ * Enhanced TypedDocumentString with type inference capabilities
7
+ * Compatible with GraphQL codegen client preset
8
+ */
9
+ export class TypedDocumentString extends String {
10
+ /** Same shape as the codegen implementation for structural typing */
11
+ __apiType;
12
+ __meta__;
13
+ value;
14
+ constructor(value, meta) {
15
+ super(value);
16
+ this.value = value;
17
+ this.__meta__ = {
18
+ hash: this.generateHash(value),
19
+ ...meta,
20
+ };
21
+ }
22
+ generateHash(value) {
23
+ let hash = 0;
24
+ for (let i = 0; i < value.length; i++) {
25
+ const char = value.charCodeAt(i);
26
+ hash = (hash << 5) - hash + char;
27
+ hash = hash & hash; // Convert to 32-bit integer
28
+ }
29
+ return Math.abs(hash).toString(36);
30
+ }
31
+ toString() {
32
+ return this.value;
33
+ }
34
+ /**
35
+ * Get the hash for caching purposes
36
+ */
37
+ getHash() {
38
+ return this.__meta__?.hash;
39
+ }
40
+ }
package/esm/custom-ast.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import * as t from 'gql-ast';
2
2
  import { Kind } from 'graphql';
3
+ /**
4
+ * Get custom AST for MetaField type - handles PostgreSQL types that need subfield selections
5
+ */
3
6
  export function getCustomAst(fieldDefn) {
4
7
  if (!fieldDefn) {
5
8
  return null;
@@ -109,6 +112,7 @@ export function geometryCollectionAst(name) {
109
112
  t.field({
110
113
  name: 'geometries',
111
114
  selectionSet: t.selectionSet({
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
116
  selections: [inlineFragment], // gql-ast limitation with inline fragments
113
117
  }),
114
118
  }),
@@ -116,6 +120,9 @@ export function geometryCollectionAst(name) {
116
120
  }),
117
121
  });
118
122
  }
123
+ /**
124
+ * Generate AST for generic geometry type (returns geojson)
125
+ */
119
126
  export function geometryAst(name) {
120
127
  return t.field({
121
128
  name,
@@ -124,6 +131,9 @@ export function geometryAst(name) {
124
131
  }),
125
132
  });
126
133
  }
134
+ /**
135
+ * Generate AST for interval type
136
+ */
127
137
  export function intervalAst(name) {
128
138
  return t.field({
129
139
  name,
@@ -142,6 +152,11 @@ export function intervalAst(name) {
142
152
  function toFieldArray(strArr) {
143
153
  return strArr.map((fieldName) => t.field({ name: fieldName }));
144
154
  }
155
+ /**
156
+ * Check if an object has interval type shape
157
+ */
145
158
  export function isIntervalType(obj) {
146
- return ['days', 'hours', 'minutes', 'months', 'seconds', 'years'].every((key) => obj.hasOwnProperty(key));
159
+ if (!obj || typeof obj !== 'object')
160
+ return false;
161
+ return ['days', 'hours', 'minutes', 'months', 'seconds', 'years'].every((key) => Object.prototype.hasOwnProperty.call(obj, key));
147
162
  }