@constructive-io/graphql-query 2.4.7 → 2.5.1

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/esm/custom-ast.js CHANGED
@@ -1,6 +1,8 @@
1
- // @ts-nocheck
2
1
  import * as t from 'gql-ast';
3
2
  export function getCustomAst(fieldDefn) {
3
+ if (!fieldDefn) {
4
+ return null;
5
+ }
4
6
  const { pgType } = fieldDefn.type;
5
7
  if (pgType === 'geometry') {
6
8
  return geometryAst(fieldDefn.name);
@@ -9,15 +11,116 @@ export function getCustomAst(fieldDefn) {
9
11
  return intervalAst(fieldDefn.name);
10
12
  }
11
13
  return t.field({
12
- name: fieldDefn.name
14
+ name: fieldDefn.name,
15
+ });
16
+ }
17
+ /**
18
+ * Generate custom AST for CleanField type - handles GraphQL types that need subfield selections
19
+ */
20
+ export function getCustomAstForCleanField(field) {
21
+ const { name, type } = field;
22
+ const { gqlType, pgType } = type;
23
+ // Handle by GraphQL type first (this is what the error messages reference)
24
+ if (gqlType === 'GeometryPoint') {
25
+ return geometryPointAst(name);
26
+ }
27
+ if (gqlType === 'Interval') {
28
+ return intervalAst(name);
29
+ }
30
+ if (gqlType === 'GeometryGeometryCollection') {
31
+ return geometryCollectionAst(name);
32
+ }
33
+ // Handle by pgType as fallback
34
+ if (pgType === 'geometry') {
35
+ return geometryAst(name);
36
+ }
37
+ if (pgType === 'interval') {
38
+ return intervalAst(name);
39
+ }
40
+ // Return simple field for scalar types
41
+ return t.field({
42
+ name,
43
+ });
44
+ }
45
+ /**
46
+ * Check if a CleanField requires subfield selection based on its GraphQL type
47
+ */
48
+ export function requiresSubfieldSelection(field) {
49
+ const { gqlType } = field.type;
50
+ // Complex GraphQL types that require subfield selection
51
+ const complexTypes = [
52
+ 'GeometryPoint',
53
+ 'Interval',
54
+ 'GeometryGeometryCollection',
55
+ 'GeoJSON',
56
+ ];
57
+ return complexTypes.includes(gqlType);
58
+ }
59
+ /**
60
+ * Generate AST for GeometryPoint type
61
+ */
62
+ export function geometryPointAst(name) {
63
+ return t.field({
64
+ name,
65
+ selectionSet: t.selectionSet({
66
+ selections: toFieldArray(['x', 'y']),
67
+ }),
68
+ });
69
+ }
70
+ /**
71
+ * Generate AST for GeometryGeometryCollection type
72
+ */
73
+ export function geometryCollectionAst(name) {
74
+ // Manually create inline fragment since gql-ast doesn't support it
75
+ const inlineFragment = {
76
+ kind: 'InlineFragment',
77
+ typeCondition: {
78
+ kind: 'NamedType',
79
+ name: {
80
+ kind: 'Name',
81
+ value: 'GeometryPoint',
82
+ },
83
+ },
84
+ selectionSet: {
85
+ kind: 'SelectionSet',
86
+ selections: [
87
+ {
88
+ kind: 'Field',
89
+ name: {
90
+ kind: 'Name',
91
+ value: 'x',
92
+ },
93
+ },
94
+ {
95
+ kind: 'Field',
96
+ name: {
97
+ kind: 'Name',
98
+ value: 'y',
99
+ },
100
+ },
101
+ ],
102
+ },
103
+ };
104
+ return t.field({
105
+ name,
106
+ selectionSet: t.selectionSet({
107
+ selections: [
108
+ t.field({
109
+ name: 'geometries',
110
+ selectionSet: t.selectionSet({
111
+ selections: [inlineFragment], // gql-ast limitation with inline fragments
112
+ }),
113
+ }),
114
+ ],
115
+ }),
13
116
  });
14
117
  }
15
118
  export function geometryAst(name) {
16
119
  return t.field({
17
120
  name,
18
121
  selectionSet: t.selectionSet({
19
- selections: toFieldArray(['geojson'])
20
- })
122
+ selections: toFieldArray(['geojson']),
123
+ }),
21
124
  });
22
125
  }
23
126
  export function intervalAst(name) {
@@ -30,21 +133,14 @@ export function intervalAst(name) {
30
133
  'minutes',
31
134
  'months',
32
135
  'seconds',
33
- 'years'
34
- ])
35
- })
136
+ 'years',
137
+ ]),
138
+ }),
36
139
  });
37
140
  }
38
141
  function toFieldArray(strArr) {
39
142
  return strArr.map((fieldName) => t.field({ name: fieldName }));
40
143
  }
41
144
  export function isIntervalType(obj) {
42
- return [
43
- 'days',
44
- 'hours',
45
- 'minutes',
46
- 'months',
47
- 'seconds',
48
- 'years'
49
- ].every((key) => obj.hasOwnProperty(key));
145
+ return ['days', 'hours', 'minutes', 'months', 'seconds', 'years'].every((key) => obj.hasOwnProperty(key));
50
146
  }
package/esm/index.js CHANGED
@@ -1,342 +1,3 @@
1
- // @ts-nocheck
2
- import { print as gqlPrint } from 'graphql';
3
- import inflection from 'inflection';
4
- import { createOne, deleteOne, getAll, getMany, getOne, patchOne } from './ast';
5
- import { validateMetaObject } from './meta-object';
1
+ export { QueryBuilder } from './query-builder';
2
+ export * from './types';
6
3
  export * as MetaObject from './meta-object';
7
- const isObject = val => val !== null && typeof val === 'object';
8
- export class QueryBuilder {
9
- constructor({ meta = {}, introspection }) {
10
- this._introspection = introspection;
11
- this._meta = meta;
12
- this.clear();
13
- this.initModelMap();
14
- this.pickScalarFields = pickScalarFields.bind(this);
15
- this.pickAllFields = pickAllFields.bind(this);
16
- const result = validateMetaObject(this._meta);
17
- if (typeof result === 'object' && result.errors) {
18
- throw new Error(`QueryBuilder: meta object is invalid:\n${result.message}`);
19
- }
20
- }
21
- /*
22
- * Save all gql queries and mutations by model name for quicker lookup
23
- */
24
- initModelMap() {
25
- this._models = Object.keys(this._introspection).reduce((map, key) => {
26
- const defn = this._introspection[key];
27
- map = {
28
- ...map,
29
- [defn.model]: {
30
- ...map[defn.model],
31
- ...{ [key]: defn }
32
- }
33
- };
34
- return map;
35
- }, {});
36
- }
37
- clear() {
38
- this._model = '';
39
- this._fields = [];
40
- this._key = null;
41
- this._queryName = '';
42
- this._ast = null;
43
- this._edges = false;
44
- }
45
- query(model) {
46
- this.clear();
47
- this._model = model;
48
- return this;
49
- }
50
- _findQuery() {
51
- // based on the op, finds the relevant GQL query
52
- const queries = this._models[this._model];
53
- if (!queries) {
54
- throw new Error('No queries found for ' + this._model);
55
- }
56
- const matchQuery = Object.entries(queries).find(([_, defn]) => defn.qtype === this._op);
57
- if (!matchQuery) {
58
- throw new Error('No query found for ' + this._model + ':' + this._op);
59
- }
60
- const queryKey = matchQuery[0];
61
- return queryKey;
62
- }
63
- _findMutation() {
64
- // For mutation, there can be many defns that match the operation being requested
65
- // .ie: deleteAction, deleteActionBySlug, deleteActionByName
66
- const matchingDefns = Object.keys(this._introspection).reduce((arr, mutationKey) => {
67
- const defn = this._introspection[mutationKey];
68
- if (defn.model === this._model &&
69
- defn.qtype === this._op &&
70
- defn.qtype === 'mutation' &&
71
- defn.mutationType === this._mutation) {
72
- arr = [...arr, { defn, mutationKey }];
73
- }
74
- return arr;
75
- }, []);
76
- if (!matchingDefns.length === 0) {
77
- throw new Error('no mutation found for ' + this._model + ':' + this._mutation);
78
- }
79
- // We only need deleteAction from all of [deleteAction, deleteActionBySlug, deleteActionByName]
80
- const getInputName = (mutationType) => {
81
- switch (mutationType) {
82
- case 'delete': {
83
- return `Delete${inflection.camelize(this._model)}Input`;
84
- }
85
- case 'create': {
86
- return `Create${inflection.camelize(this._model)}Input`;
87
- }
88
- case 'patch': {
89
- return `Update${inflection.camelize(this._model)}Input`;
90
- }
91
- default:
92
- throw new Error('Unhandled mutation type' + mutationType);
93
- }
94
- };
95
- const matchDefn = matchingDefns.find(({ defn }) => defn.properties.input.type === getInputName(this._mutation));
96
- if (!matchDefn) {
97
- throw new Error('no mutation found for ' + this._model + ':' + this._mutation);
98
- }
99
- return matchDefn.mutationKey;
100
- }
101
- select(selection) {
102
- const defn = this._introspection[this._key];
103
- // If selection not given, pick only scalar fields
104
- if (selection == null) {
105
- this._select = this.pickScalarFields(null, defn);
106
- return this;
107
- }
108
- this._select = this.pickAllFields(selection, defn);
109
- return this;
110
- }
111
- edges(useEdges) {
112
- this._edges = useEdges;
113
- return this;
114
- }
115
- getMany({ select } = {}) {
116
- this._op = 'getMany';
117
- this._key = this._findQuery();
118
- this.queryName(inflection.camelize(['get', inflection.underscore(this._key), 'query'].join('_'), true));
119
- const defn = this._introspection[this._key];
120
- this.select(select);
121
- this._ast = getMany({
122
- builder: this,
123
- queryName: this._queryName,
124
- operationName: this._key,
125
- query: defn,
126
- selection: this._select
127
- });
128
- return this;
129
- }
130
- all({ select } = {}) {
131
- this._op = 'getMany';
132
- this._key = this._findQuery();
133
- this.queryName(inflection.camelize(['get', inflection.underscore(this._key), 'query', 'all'].join('_'), true));
134
- const defn = this._introspection[this._key];
135
- this.select(select);
136
- this._ast = getAll({
137
- builder: this,
138
- queryName: this._queryName,
139
- operationName: this._key,
140
- query: defn,
141
- selection: this._select
142
- });
143
- return this;
144
- }
145
- getOne({ select } = {}) {
146
- this._op = 'getOne';
147
- this._key = this._findQuery();
148
- this.queryName(inflection.camelize(['get', inflection.underscore(this._key), 'query'].join('_'), true));
149
- const defn = this._introspection[this._key];
150
- this.select(select);
151
- this._ast = getOne({
152
- builder: this,
153
- queryName: this._queryName,
154
- operationName: this._key,
155
- query: defn,
156
- selection: this._select
157
- });
158
- return this;
159
- }
160
- create({ select } = {}) {
161
- this._op = 'mutation';
162
- this._mutation = 'create';
163
- this._key = this._findMutation();
164
- this.queryName(inflection.camelize([inflection.underscore(this._key), 'mutation'].join('_'), true));
165
- const defn = this._introspection[this._key];
166
- this.select(select);
167
- this._ast = createOne({
168
- builder: this,
169
- operationName: this._key,
170
- mutationName: this._queryName,
171
- mutation: defn,
172
- selection: this._select
173
- });
174
- return this;
175
- }
176
- delete({ select } = {}) {
177
- this._op = 'mutation';
178
- this._mutation = 'delete';
179
- this._key = this._findMutation();
180
- this.queryName(inflection.camelize([inflection.underscore(this._key), 'mutation'].join('_'), true));
181
- const defn = this._introspection[this._key];
182
- this.select(select);
183
- this._ast = deleteOne({
184
- builder: this,
185
- operationName: this._key,
186
- mutationName: this._queryName,
187
- mutation: defn,
188
- selection: this._select
189
- });
190
- return this;
191
- }
192
- update({ select } = {}) {
193
- this._op = 'mutation';
194
- this._mutation = 'patch';
195
- this._key = this._findMutation();
196
- this.queryName(inflection.camelize([inflection.underscore(this._key), 'mutation'].join('_'), true));
197
- const defn = this._introspection[this._key];
198
- this.select(select);
199
- this._ast = patchOne({
200
- builder: this,
201
- operationName: this._key,
202
- mutationName: this._queryName,
203
- mutation: defn,
204
- selection: this._select
205
- });
206
- return this;
207
- }
208
- queryName(name) {
209
- this._queryName = name;
210
- return this;
211
- }
212
- print() {
213
- this._hash = gqlPrint(this._ast);
214
- return this;
215
- }
216
- }
217
- /**
218
- * Pick scalar fields of a query definition
219
- * @param {Object} defn Query definition
220
- * @param {Object} meta Meta object containing info about table relations
221
- * @returns {Array}
222
- */
223
- function pickScalarFields(selection, defn) {
224
- const model = defn.model;
225
- const modelMeta = this._meta.tables.find((t) => t.name === model);
226
- const isInTableSchema = (fieldName) => !!modelMeta.fields.find((field) => field.name === fieldName);
227
- const pickFrom = (modelSelection) => modelSelection
228
- .filter((fieldName) => {
229
- // If not specified or not a valid selection list, allow all
230
- if (selection == null || !Array.isArray(selection))
231
- return true;
232
- return selection.includes(fieldName);
233
- })
234
- .filter((fieldName) => !isRelationalField(fieldName, modelMeta) && isInTableSchema(fieldName))
235
- .map((fieldName) => ({
236
- name: fieldName,
237
- isObject: false,
238
- fieldDefn: modelMeta.fields.find((f) => f.name === fieldName)
239
- }));
240
- // This is for inferring the sub-selection of a mutation query
241
- // from a definition model .eg UserSetting, find its related queries in the introspection object, and pick its selection fields
242
- if (defn.qtype === 'mutation') {
243
- const relatedQuery = this._introspection[`${modelNameToGetMany(defn.model)}`];
244
- return pickFrom(relatedQuery.selection);
245
- }
246
- return pickFrom(defn.selection);
247
- }
248
- /**
249
- * Pick scalar fields and sub-selection fields of a query definition
250
- * @param {Object} selection Selection clause object
251
- * @param {Object} defn Query definition
252
- * @param {Object} meta Meta object containing info about table relations
253
- * @returns {Array}
254
- */
255
- function pickAllFields(selection, defn) {
256
- const model = defn.model;
257
- const modelMeta = this._meta.tables.find((t) => t.name === model);
258
- const selectionEntries = Object.entries(selection);
259
- let fields = [];
260
- const isWhiteListed = (selectValue) => {
261
- return typeof selectValue === 'boolean' && selectValue;
262
- };
263
- for (const entry of selectionEntries) {
264
- const [fieldName, fieldOptions] = entry;
265
- // Case
266
- // {
267
- // goalResults: // fieldName
268
- // { select: { id: true }, variables: { first: 100 } } // fieldOptions
269
- // }
270
- if (isObject(fieldOptions)) {
271
- if (!isFieldInDefinition(fieldName, defn, modelMeta, this)) {
272
- continue;
273
- }
274
- const referencedForeignConstraint = modelMeta.foreignConstraints.find((constraint) => constraint.fromKey.name === fieldName ||
275
- constraint.fromKey.alias === fieldName);
276
- const subFields = Object.keys(fieldOptions.select).filter((subField) => {
277
- return (!isRelationalField(subField, modelMeta) &&
278
- isWhiteListed(fieldOptions.select[subField]));
279
- });
280
- const isBelongTo = !!referencedForeignConstraint;
281
- const fieldSelection = {
282
- name: fieldName,
283
- isObject: true,
284
- isBelongTo,
285
- selection: subFields,
286
- variables: fieldOptions.variables
287
- };
288
- // Need to further expand selection of object fields,
289
- // but only non-graphql-builtin, non-relation fields
290
- // .ie action { id location }
291
- // location is non-scalar and non-relational, thus need to further expand into { x y ... }
292
- if (isBelongTo) {
293
- const getManyName = modelNameToGetMany(referencedForeignConstraint.refTable);
294
- const refDefn = this._introspection[getManyName];
295
- fieldSelection.selection = pickScalarFields.call(this, subFields, refDefn);
296
- }
297
- fields = [...fields, fieldSelection];
298
- }
299
- else {
300
- // Case
301
- // {
302
- // userId: true // [fieldName, fieldOptions]
303
- // }
304
- if (isWhiteListed(fieldOptions)) {
305
- fields = [
306
- ...fields,
307
- {
308
- name: fieldName,
309
- isObject: false,
310
- fieldDefn: modelMeta.fields.find((f) => f.name === fieldName)
311
- }
312
- ];
313
- }
314
- }
315
- }
316
- return fields;
317
- }
318
- function isFieldInDefinition(fieldName, defn, modelMeta) {
319
- const isReferenced = !!modelMeta.foreignConstraints.find((constraint) => constraint.fromKey.name === fieldName ||
320
- constraint.fromKey.alias === fieldName);
321
- return (isReferenced ||
322
- defn.selection.some((selectionItem) => {
323
- if (typeof selectionItem === 'string') {
324
- return fieldName === selectionItem;
325
- }
326
- if (isObject(selectionItem)) {
327
- return selectionItem.name === fieldName;
328
- }
329
- return false;
330
- }));
331
- }
332
- // TODO: see if there is a possibility of supertyping table (a key is both a foreign and primary key)
333
- // A relational field is a foreign key but not a primary key
334
- function isRelationalField(fieldName, modelMeta) {
335
- return (!modelMeta.primaryConstraints.find((field) => field.name === fieldName) &&
336
- !!modelMeta.foreignConstraints.find((constraint) => constraint.fromKey.name === fieldName));
337
- }
338
- // Get getMany op name from model
339
- // ie. UserSetting => userSettings
340
- function modelNameToGetMany(model) {
341
- return inflection.camelize(inflection.pluralize(inflection.underscore(model)), true);
342
- }
@@ -1,8 +1,7 @@
1
- // @ts-nocheck
2
1
  export function convertFromMetaSchema(metaSchema) {
3
- const { _meta: { tables } } = metaSchema;
2
+ const { _meta: { tables }, } = metaSchema;
4
3
  const result = {
5
- tables: []
4
+ tables: [],
6
5
  };
7
6
  for (const table of tables) {
8
7
  result.tables.push({
@@ -10,7 +9,7 @@ export function convertFromMetaSchema(metaSchema) {
10
9
  fields: table.fields.map((f) => pickField(f)),
11
10
  primaryConstraints: pickArrayConstraint(table.primaryKeyConstraints),
12
11
  uniqueConstraints: pickArrayConstraint(table.uniqueConstraints),
13
- foreignConstraints: pickForeignConstraint(table.foreignKeyConstraints, table.relations)
12
+ foreignConstraints: pickForeignConstraint(table.foreignKeyConstraints, table.relations),
14
13
  });
15
14
  }
16
15
  return result;
@@ -19,7 +18,7 @@ function pickArrayConstraint(constraints) {
19
18
  if (constraints.length === 0)
20
19
  return [];
21
20
  const c = constraints[0];
22
- return c.fields.map((field) => pickField(field));
21
+ return c.fields.map((field) => pickConstraintField(field));
23
22
  }
24
23
  function pickForeignConstraint(constraints, relations) {
25
24
  if (constraints.length === 0)
@@ -29,8 +28,8 @@ function pickForeignConstraint(constraints, relations) {
29
28
  const { fields, refFields, refTable } = c;
30
29
  const fromKey = pickField(fields[0]);
31
30
  const toKey = pickField(refFields[0]);
32
- const matchingBelongsTo = belongsTo.find((c) => {
33
- const field = pickField(c.keys[0]);
31
+ const matchingBelongsTo = belongsTo.find((belongsToItem) => {
32
+ const field = pickField(belongsToItem.keys[0]);
34
33
  return field.name === fromKey.name;
35
34
  });
36
35
  // Ex: 'ownerId' will have an alias of 'owner', which has further selection of 'User' type
@@ -40,13 +39,19 @@ function pickForeignConstraint(constraints, relations) {
40
39
  return {
41
40
  refTable: refTable.name,
42
41
  fromKey,
43
- toKey
42
+ toKey,
44
43
  };
45
44
  });
46
45
  }
47
46
  function pickField(field) {
48
47
  return {
49
48
  name: field.name,
50
- type: field.type
49
+ type: field.type,
50
+ };
51
+ }
52
+ function pickConstraintField(field) {
53
+ return {
54
+ name: field.name,
55
+ type: field.type,
51
56
  };
52
57
  }
@@ -5,27 +5,44 @@
5
5
  "definitions": {
6
6
  "field": {
7
7
  "type": "object",
8
- "required": ["name", "type"],
8
+ "required": [
9
+ "name",
10
+ "type"
11
+ ],
9
12
  "additionalProperties": true,
10
13
  "properties": {
11
14
  "name": {
12
15
  "type": "string",
13
16
  "default": "id",
14
- "examples": ["id"]
17
+ "examples": [
18
+ "id"
19
+ ]
15
20
  },
16
21
  "type": {
17
22
  "type": "object",
18
- "required": ["pgType", "gqlType"],
23
+ "required": [
24
+ "pgType",
25
+ "gqlType"
26
+ ],
19
27
  "additionalProperties": true,
20
28
  "properties": {
21
29
  "gqlType": {
22
- "type": ["string", "null"]
30
+ "type": [
31
+ "string",
32
+ "null"
33
+ ]
23
34
  },
24
35
  "pgType": {
25
- "type": ["string", "null"]
36
+ "type": [
37
+ "string",
38
+ "null"
39
+ ]
26
40
  },
27
41
  "subtype": {
28
- "type": ["string", "null"]
42
+ "type": [
43
+ "string",
44
+ "null"
45
+ ]
29
46
  }
30
47
  }
31
48
  }
@@ -41,19 +58,27 @@
41
58
  "name": {
42
59
  "type": "string",
43
60
  "default": "User",
44
- "examples": ["User"]
61
+ "examples": [
62
+ "User"
63
+ ]
45
64
  },
46
65
  "fields": {
47
66
  "type": "array",
48
- "items": { "$ref": "#/definitions/field" }
67
+ "items": {
68
+ "$ref": "#/definitions/field"
69
+ }
49
70
  },
50
71
  "primaryConstraints": {
51
72
  "type": "array",
52
- "items": { "$ref": "#/definitions/field" }
73
+ "items": {
74
+ "$ref": "#/definitions/field"
75
+ }
53
76
  },
54
77
  "uniqueConstraints": {
55
78
  "type": "array",
56
- "items": { "$ref": "#/definitions/field" }
79
+ "items": {
80
+ "$ref": "#/definitions/field"
81
+ }
57
82
  },
58
83
  "foreignConstraints": {
59
84
  "type": "array",
@@ -63,21 +88,36 @@
63
88
  "refTable": {
64
89
  "type": "string",
65
90
  "default": "User",
66
- "examples": ["User"]
91
+ "examples": [
92
+ "User"
93
+ ]
94
+ },
95
+ "fromKey": {
96
+ "$ref": "#/definitions/field"
67
97
  },
68
- "fromKey": { "$ref": "#/definitions/field" },
69
- "toKey": { "$ref": "#/definitions/field" }
98
+ "toKey": {
99
+ "$ref": "#/definitions/field"
100
+ }
70
101
  },
71
- "required": ["refTable", "fromKey", "toKey"],
102
+ "required": [
103
+ "refTable",
104
+ "fromKey",
105
+ "toKey"
106
+ ],
72
107
  "additionalProperties": false
73
108
  }
74
109
  }
75
110
  },
76
- "required": ["name", "fields"],
111
+ "required": [
112
+ "name",
113
+ "fields"
114
+ ],
77
115
  "additionalProperties": false
78
116
  }
79
117
  }
80
118
  },
81
- "required": ["tables"],
119
+ "required": [
120
+ "tables"
121
+ ],
82
122
  "additionalProperties": false
83
123
  }
@@ -7,6 +7,6 @@ export function validateMetaObject(obj) {
7
7
  return true;
8
8
  return {
9
9
  errors: ajv.errors,
10
- message: ajv.errorsText(ajv.errors, { separator: '\n' })
10
+ message: ajv.errorsText(ajv.errors, { separator: '\n' }),
11
11
  };
12
12
  }