orchid-graphql 0.0.4 → 1.0.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/dist/index.js CHANGED
@@ -1,26 +1,4 @@
1
- // src/paginators/base.ts
2
- var s = Symbol();
3
- function set_query_page_result(query, get_page) {
4
- function _get_page(data) {
5
- if (!Array.isArray(data)) {
6
- throw new Error(`Paginator called for single result query.`);
7
- }
8
- return get_page(data);
9
- }
10
- const _query = query.query;
11
- const { handleResult } = _query;
12
- _query.handleResult = (q, result, s2) => {
13
- const data = handleResult(q, result, s2);
14
- return s2 ? data : _get_page(data);
15
- };
16
- _query[s] = _get_page;
17
- return query;
18
- }
19
- function get_query_pagination_handler(query) {
20
- return query.query[s];
21
- }
22
-
23
- // src/paginators/cursor.ts
1
+ // ../graphql-orm-resolver/src/paginators/cursor.ts
24
2
  function defineCursorPaginator(options = {}) {
25
3
  return new CursorPaginator(options);
26
4
  }
@@ -42,18 +20,22 @@ var CursorPaginator = class {
42
20
  }
43
21
  });
44
22
  }
45
- paginate(query, args = {}) {
23
+ paginate(query, context) {
24
+ const { orm: orm2 } = context.graph;
25
+ const { args } = context.tree;
46
26
  const take = args.take ?? this.options.take;
47
- const { cursor } = args;
48
- query = query.clear("order");
27
+ const cursor = args.cursor;
28
+ query = orm2.reset_query_order(query);
49
29
  for (const field of this.fields) {
50
- query = query.select(field.name).order({ [field.name]: field.desc ? "DESC" : "ASC" });
30
+ query = orm2.select_field(query, { field: field.name, as: field.name });
31
+ query = orm2.add_query_order(query, field.name, field.desc);
51
32
  }
52
33
  if (cursor) {
53
- query = this._set_query_cursor(query, cursor);
34
+ const { expression, bindings } = this._parse_cursor(cursor);
35
+ query = orm2.where_raw(query, expression, bindings);
54
36
  }
55
- query = query.limit(take + 1);
56
- query = set_query_page_result(query, (nodes) => {
37
+ query = orm2.set_query_limit(query, take + 1);
38
+ query = orm2.set_query_page_result(query, (nodes) => {
57
39
  let cursor2;
58
40
  if (nodes.length > take) {
59
41
  cursor2 = this._create_cursor(nodes[take - 1]);
@@ -76,29 +58,29 @@ var CursorPaginator = class {
76
58
  })
77
59
  );
78
60
  }
79
- _set_query_cursor(query, cursor) {
61
+ _parse_cursor(cursor) {
80
62
  const values = JSON.parse(cursor);
81
63
  const left = [];
82
64
  const right = [];
83
- const expr_values = {};
65
+ const bindings = {};
84
66
  for (let i = 0; i < this.fields.length; ++i) {
85
67
  const field = this.fields[i];
86
68
  const expressions = field.desc ? right : left;
87
69
  const placeholders = field.desc ? left : right;
88
70
  expressions.push(`"${field.name}"`);
89
- const placeholder = `value${i}`;
90
- placeholders.push("$" + placeholder);
91
- expr_values[placeholder] = values[i];
71
+ placeholders.push("$" + field.name);
72
+ bindings[field.name] = values[i];
92
73
  }
93
- return query.where(
94
- query.raw(`(${left.join(",")}) > (${right.join(",")})`, expr_values)
95
- );
74
+ return {
75
+ expression: `(${left.join(",")}) > (${right.join(",")})`,
76
+ bindings
77
+ };
96
78
  }
97
79
  };
98
80
 
99
- // src/utils/run-after.ts
100
- function run_after_query(query, transform) {
101
- return query.afterQuery(async (_, result) => {
81
+ // ../graphql-orm-resolver/src/utils/run-after.ts
82
+ function run_after_query(orm2, query, transform) {
83
+ return orm2.run_after_query(query, async (result) => {
102
84
  if (Array.isArray(result)) {
103
85
  const result1 = await Promise.all(result.map(transform));
104
86
  return result1.filter(Boolean);
@@ -110,7 +92,7 @@ function run_after_query(query, transform) {
110
92
  });
111
93
  }
112
94
 
113
- // src/resolvers/field.ts
95
+ // ../graphql-orm-resolver/src/resolvers/field.ts
114
96
  function defineFieldResolver(options = {}) {
115
97
  const { tableField, modify, transform } = options;
116
98
  return function resolve(query, context) {
@@ -118,10 +100,13 @@ function defineFieldResolver(options = {}) {
118
100
  if (modify) {
119
101
  query = modify(query, context);
120
102
  } else {
121
- query = query.select({ [field]: tableField || field });
103
+ query = context.graph.orm.select_field(query, {
104
+ field: tableField || field,
105
+ as: field
106
+ });
122
107
  }
123
108
  if (transform) {
124
- query = run_after_query(query, async (instance) => {
109
+ query = run_after_query(context.graph.orm, query, async (instance) => {
125
110
  instance[field] = await transform(instance[field], instance, context);
126
111
  return instance;
127
112
  });
@@ -130,14 +115,12 @@ function defineFieldResolver(options = {}) {
130
115
  };
131
116
  }
132
117
 
133
- // src/resolvers/graph.ts
118
+ // ../graphql-orm-resolver/src/resolvers/graph.ts
134
119
  import { parseResolveInfo } from "graphql-parse-resolve-info";
135
- function createGraphResolver(types, options) {
136
- return new GraphResolver(types, options);
137
- }
138
120
  var GraphResolver = class {
139
- constructor(tables, options = {}) {
140
- this.tables = tables;
121
+ constructor(orm2, type_resolvers, options = {}) {
122
+ this.orm = orm2;
123
+ this.type_resolvers = type_resolvers;
141
124
  this.options = options;
142
125
  }
143
126
  resolve(query, { info, context, filters }) {
@@ -154,87 +137,81 @@ var GraphResolver = class {
154
137
  _resolve_type(query, context) {
155
138
  const { tree } = context;
156
139
  const type = Object.keys(tree.fieldsByTypeName)[0];
157
- const table_resolver = this.tables[type];
158
- if (!table_resolver) {
159
- throw new Error(`Table resolver not found for type ${type}.`);
140
+ const type_resolver = this.type_resolvers[type];
141
+ if (!type_resolver) {
142
+ throw new Error(`Resolver not found for type ${type}.`);
160
143
  }
161
- return table_resolver(query, { ...context, graph: this, tree, type });
144
+ return type_resolver.resolve(query, {
145
+ ...context,
146
+ graph: this,
147
+ tree,
148
+ type
149
+ });
162
150
  }
163
151
  _resolve_page(query, paginator, context) {
164
152
  let { tree } = context;
165
- const { args } = tree;
166
153
  for (const field of paginator.path) {
167
154
  const type = Object.keys(tree.fieldsByTypeName)[0];
168
155
  tree = tree.fieldsByTypeName[type][field];
169
156
  }
170
157
  query = this._resolve_type(query, { ...context, tree });
171
- return paginator.paginate(query, args);
158
+ return paginator.paginate(query, {
159
+ ...context,
160
+ graph: this
161
+ });
172
162
  }
173
163
  };
174
164
 
175
- // src/resolvers/page.ts
176
- import { overrideParserInQuery } from "orchid-core";
165
+ // ../graphql-orm-resolver/src/resolvers/page.ts
177
166
  function definePageResolver(paginator, options = {}) {
178
167
  const { tableField, modify } = options;
179
- return defineFieldResolver({
180
- modify(query, context) {
181
- const { field, graph } = context;
182
- let get_page;
183
- query = query.select({
184
- [field]: (q) => {
185
- q = graph._resolve_page(
186
- q[tableField || field],
187
- paginator,
188
- context
189
- );
190
- get_page = get_query_pagination_handler(q);
191
- return modify ? modify(q, context) : q;
192
- }
193
- });
194
- overrideParserInQuery(query.query, field, get_page);
195
- return query;
196
- }
197
- });
168
+ return function resolve(query, context) {
169
+ const { graph, field } = context;
170
+ const pagination_context = {};
171
+ query = graph.orm.select_relation(query, {
172
+ relation: tableField || field,
173
+ as: field,
174
+ modify(subquery) {
175
+ subquery = graph._resolve_page(subquery, paginator, context);
176
+ subquery = graph.orm.modify_subquery_pagination(
177
+ subquery,
178
+ pagination_context
179
+ );
180
+ return modify ? modify(subquery, context) : subquery;
181
+ }
182
+ });
183
+ query = graph.orm.finish_query_pagination(query, field, pagination_context);
184
+ return query;
185
+ };
198
186
  }
199
187
 
200
- // src/resolvers/relation.ts
188
+ // ../graphql-orm-resolver/src/resolvers/relation.ts
201
189
  function defineRelationResolver(options = {}) {
202
190
  const { tableField, filters, modify } = options;
203
- return defineFieldResolver({
204
- modify(query, context) {
205
- const { graph, field } = context;
206
- return query.select({
207
- [field]: (q) => {
208
- const relation_query = graph._resolve_type(
209
- q[tableField || field],
210
- { ...context, filters }
211
- );
212
- return modify ? modify(relation_query, context) : relation_query;
213
- }
214
- });
215
- }
216
- });
191
+ return function resolve(query, context) {
192
+ const { graph, field } = context;
193
+ return graph.orm.select_relation(query, {
194
+ relation: tableField || field,
195
+ as: field,
196
+ modify(subquery) {
197
+ subquery = graph._resolve_type(subquery, { ...context, filters });
198
+ return modify ? modify(subquery, context) : subquery;
199
+ }
200
+ });
201
+ };
217
202
  }
218
203
 
219
- // src/utils/is-plain-object.ts
204
+ // ../graphql-orm-resolver/src/utils/is-plain-object.ts
220
205
  function is_plain_object(obj) {
221
206
  return typeof obj === "object" && !Array.isArray(obj);
222
207
  }
223
208
 
224
- // src/filters/filters.ts
225
- function is_scalar(value) {
226
- return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
227
- }
228
- function apply_filters({
229
- query,
230
- filters,
231
- args,
232
- modifiers
233
- }) {
209
+ // ../graphql-orm-resolver/src/filters/filters.ts
210
+ function apply_filters(query, { filters, modifiers, context }) {
234
211
  if (!filters) {
235
212
  return query;
236
213
  }
237
- const filter_obj = args?.filter;
214
+ const filter_obj = context.tree.args?.filter;
238
215
  if (!filter_obj) {
239
216
  return query;
240
217
  }
@@ -246,90 +223,60 @@ function apply_filters({
246
223
  continue;
247
224
  }
248
225
  if (modifiers?.[field]) {
249
- query = modifiers[field](query, value);
226
+ query = modifiers[field](query, value, context);
250
227
  } else {
251
- const [dbfield, op0] = field.split("__");
252
- const op = op0?.toLowerCase() || "exact";
253
- if (
254
- // TODO: add the rest of orchid ops (or allow all of them?)
255
- [
256
- "exact",
257
- "lt",
258
- "lte",
259
- "gt",
260
- "gte",
261
- "like",
262
- "ilike",
263
- "contains"
264
- ].includes(op)
265
- ) {
266
- if (!is_scalar(value)) {
267
- throw new Error(
268
- `Unsupported filter value for ${query.table}.${field}: must be scalar.`
269
- );
270
- }
271
- if (op === "exact") {
272
- query = query.where({ [dbfield]: value });
273
- } else {
274
- const query_op = { like: "contains" }[op] || op;
275
- query = query.where({ [dbfield]: { [query_op]: value } });
276
- }
277
- } else if (op === "in") {
278
- if (!(Array.isArray(value) && value.every(is_scalar))) {
279
- throw new Error(
280
- `Invalid filter value for ${query.table}.${field}: must be an array of scalars.`
281
- );
282
- }
283
- query = query.whereIn(dbfield, value);
284
- } else {
285
- throw new Error(
286
- `Invalid filter ${query.table}.${field}: unsupported operator ${op}.`
287
- );
288
- }
228
+ const [table_field, op0] = field.split("__");
229
+ const op = op0?.toLowerCase();
230
+ query = context.graph.orm.where(query, table_field, op, value);
289
231
  }
290
232
  }
291
233
  return query;
292
234
  }
293
235
 
294
- // src/resolvers/table.ts
295
- function defineTableResolver(table, options = {}) {
296
- const get_default_field_resolver = (field, tableField) => {
297
- const table_field_lookup = tableField || field;
298
- if (options.modifiers?.[table_field_lookup]) {
299
- return (query) => query;
300
- } else if (table.relations?.[table_field_lookup]) {
301
- return defineRelationResolver({ tableField });
302
- } else {
303
- return defineFieldResolver({ tableField });
236
+ // ../graphql-orm-resolver/src/resolvers/table.ts
237
+ var TableResolver = class {
238
+ constructor(orm2, table, options = {}) {
239
+ this.orm = orm2;
240
+ this.table = table;
241
+ this.options = options;
242
+ this.relations = new Set(orm2.get_table_relations(table));
243
+ this.virtual_fields = new Set(orm2.get_table_virtual_fields(table));
244
+ this.modifiers = { ...orm2.get_table_modifiers(table), ...options.modifiers };
245
+ const { fields } = options;
246
+ if (fields) {
247
+ this.table_field_resolvers = Object.keys(fields).reduce((resolvers, field) => {
248
+ const r0 = fields[field];
249
+ const r = typeof r0 === "function" ? r0 : r0 === true ? this._get_default_field_resolver(field) : typeof r0 === "string" ? this._get_default_field_resolver(field, r0) : void 0;
250
+ if (r === void 0) {
251
+ throw new Error(
252
+ `Field resolver must be a function, string, or true; found ${typeof r0}.`
253
+ );
254
+ }
255
+ resolvers[field] = r;
256
+ return resolvers;
257
+ }, {});
304
258
  }
305
- };
306
- const { fields } = options;
307
- const table_field_resolvers = fields ? Object.keys(fields).reduce(
308
- (resolvers, field) => {
309
- const r0 = fields[field];
310
- const r = typeof r0 === "function" ? r0 : r0 === true ? get_default_field_resolver(field) : typeof r0 === "string" ? get_default_field_resolver(field, r0) : void 0;
311
- if (r === void 0) {
312
- throw new Error(
313
- `Field resolver must be a function, string, or true; found ${r0}`
314
- );
315
- }
316
- resolvers[field] = r;
317
- return resolvers;
318
- },
319
- {}
320
- ) : void 0;
321
- const { modify, transform } = options;
322
- return function resolve(query, context) {
259
+ }
260
+ // Introspection results
261
+ relations;
262
+ virtual_fields;
263
+ modifiers;
264
+ table_field_resolvers;
265
+ /** Modify query to select fields/relations and filter the result set. */
266
+ resolve(query, context) {
267
+ const { modify, transform } = this.options;
323
268
  const { graph, tree, type, filters } = context;
324
- if (query.table !== table.table) {
269
+ const query_table = this.orm.get_query_table(query);
270
+ const table_table = this.orm.get_table_table(this.table);
271
+ if (query_table !== table_table) {
325
272
  throw new Error(
326
- `Mismatching query type (expected ${table.table}, found ${query.table}).`
273
+ `Mismatching query table for type ${type}: expected ${table_table}, received ${query_table}.`
327
274
  );
328
275
  }
329
- const allow_all_fields = options.allowAllFields ?? graph.options.allowAllFields ?? table_field_resolvers === void 0;
330
- if (!allow_all_fields && !table_field_resolvers) {
276
+ const allow_all_fields = this.options.allowAllFields ?? graph.options.allowAllFields ?? !this.options.fields;
277
+ if (!allow_all_fields && !this.table_field_resolvers) {
331
278
  throw new Error(
332
- `Table resolver for ${type} must either allow all fields or specify options.fields`
279
+ `Resolver for type ${type} must either allow all fields or specify options.fields.`
333
280
  );
334
281
  }
335
282
  if (modify) {
@@ -337,31 +284,135 @@ function defineTableResolver(table, options = {}) {
337
284
  }
338
285
  for (const subtree of Object.values(tree.fieldsByTypeName[type])) {
339
286
  const field = subtree.name;
340
- const r = table_field_resolvers?.[field] || (allow_all_fields ? get_default_field_resolver(field) : void 0);
287
+ const r = this.table_field_resolvers?.[field] || (allow_all_fields ? this._get_default_field_resolver(field) : void 0);
341
288
  if (!r) {
342
- throw new Error(`No field resolver defined for field ${type}.${field}`);
289
+ throw new Error(`No field resolver defined for field ${type}.${field}.`);
343
290
  }
344
291
  if (r) {
345
292
  query = r(query, { ...context, tree: subtree, field });
346
293
  }
347
294
  }
348
- const allow_all_filters = options.allowAllFilters ?? graph.options.allowAllFilters;
295
+ const allow_all_filters = this.options.allowAllFilters ?? graph.options.allowAllFilters;
349
296
  const effective_filters = allow_all_filters ? true : filters;
350
297
  if (effective_filters) {
351
- query = apply_filters({
352
- query,
298
+ query = apply_filters(query, {
353
299
  filters: effective_filters,
354
- args: tree.args,
355
- modifiers: options.modifiers
300
+ modifiers: this.modifiers,
301
+ context
356
302
  });
357
303
  }
358
304
  if (transform) {
359
- query = run_after_query(query, (instance) => {
305
+ query = run_after_query(this.orm, query, (instance) => {
360
306
  return transform(instance, context);
361
307
  });
362
308
  }
309
+ query = this.orm.prevent_select_all(query);
363
310
  return query;
364
- };
311
+ }
312
+ /**
313
+ * Create field resolver which will modify query to resolve GraphQL field.
314
+ */
315
+ _get_default_field_resolver(field, tableField) {
316
+ const table_field_lookup = tableField || field;
317
+ if (this.options.modifiers?.[table_field_lookup] || this.virtual_fields.has(table_field_lookup)) {
318
+ return (query) => query;
319
+ } else if (this.relations.has(table_field_lookup)) {
320
+ return defineRelationResolver({ tableField });
321
+ } else {
322
+ return defineFieldResolver({ tableField });
323
+ }
324
+ }
325
+ };
326
+
327
+ // src/orm/orm.ts
328
+ import { overrideParserInQuery } from "orchid-core";
329
+ var orm = {
330
+ // Reflection
331
+ get_table_table(table) {
332
+ return table.table;
333
+ },
334
+ get_table_relations(table) {
335
+ return Object.keys(table.relations);
336
+ },
337
+ get_table_virtual_fields() {
338
+ return [];
339
+ },
340
+ get_table_modifiers() {
341
+ return void 0;
342
+ },
343
+ get_query_table(query) {
344
+ if (!query.table) {
345
+ throw new Error("Query must have table.");
346
+ }
347
+ return query.table;
348
+ },
349
+ // Select
350
+ select_field(query, { field, as }) {
351
+ return query.select({ [as]: field });
352
+ },
353
+ select_relation(query, { relation, as, modify }) {
354
+ return query.select({
355
+ [as]: (q) => modify(q[relation])
356
+ });
357
+ },
358
+ // Find
359
+ where(query, field, op, value) {
360
+ return query.where({ [field]: op ? { [op]: value } : value });
361
+ },
362
+ where_raw(query, expression, bindings) {
363
+ return query.where(query.raw(expression, bindings));
364
+ },
365
+ // Order & Limit
366
+ reset_query_order(query) {
367
+ return query.clear("order");
368
+ },
369
+ add_query_order(query, field, desc) {
370
+ return query.order({ [field]: desc ? "DESC" : "ASC" });
371
+ },
372
+ set_query_limit(query, limit) {
373
+ return query.limit(limit);
374
+ },
375
+ // Pagination helpers
376
+ set_query_page_result(query, get_page) {
377
+ const _query = query.query;
378
+ const { handleResult } = _query;
379
+ _query.handleResult = (q, result, s2) => {
380
+ const data = handleResult(q, result, s2);
381
+ return s2 ? data : get_page(data);
382
+ };
383
+ _query[s] = get_page;
384
+ return query;
385
+ },
386
+ modify_subquery_pagination(subquery, context) {
387
+ context.get_page = subquery.query[s];
388
+ return subquery;
389
+ },
390
+ finish_query_pagination(query, field, context) {
391
+ const { get_page } = context;
392
+ if (!get_page) {
393
+ throw new Error(`Internal error: paginator didn't provide page callback.`);
394
+ }
395
+ overrideParserInQuery(query.query, field, get_page);
396
+ return query;
397
+ },
398
+ // Misc
399
+ run_after_query(query, fn) {
400
+ return query.afterQuery(async (_, result) => fn(result));
401
+ },
402
+ prevent_select_all(query) {
403
+ return query;
404
+ }
405
+ };
406
+ var s = Symbol("subquery_pagination_handler");
407
+
408
+ // src/resolvers/graph.ts
409
+ function createGraphResolver(types, options) {
410
+ return new GraphResolver(orm, types, options);
411
+ }
412
+
413
+ // src/resolvers/table.ts
414
+ function defineTableResolver(table, options = {}) {
415
+ return new TableResolver(orm, table, options);
365
416
  }
366
417
  export {
367
418
  createGraphResolver,