orchid-graphql 0.0.3 → 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,39 +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/query-context.ts
100
- var graphql = Symbol("graphql");
101
- function set_query_context(query, context) {
102
- ;
103
- query.query[graphql] = context;
104
- }
105
- function get_query_context(query) {
106
- return query.query[graphql];
107
- }
108
-
109
- // src/utils/run-after.ts
110
- function run_after_query(query, transform) {
111
- 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) => {
112
84
  if (Array.isArray(result)) {
113
85
  const result1 = await Promise.all(result.map(transform));
114
86
  return result1.filter(Boolean);
@@ -120,19 +92,21 @@ function run_after_query(query, transform) {
120
92
  });
121
93
  }
122
94
 
123
- // src/resolvers/field.ts
95
+ // ../graphql-orm-resolver/src/resolvers/field.ts
124
96
  function defineFieldResolver(options = {}) {
125
97
  const { tableField, modify, transform } = options;
126
- return function resolve(query, options2) {
127
- const { field } = options2;
98
+ return function resolve(query, context) {
99
+ const { field } = context;
128
100
  if (modify) {
129
- return modify(query, options2);
101
+ query = modify(query, context);
130
102
  } else {
131
- query = query.select({ [field]: tableField || field });
103
+ query = context.graph.orm.select_field(query, {
104
+ field: tableField || field,
105
+ as: field
106
+ });
132
107
  }
133
108
  if (transform) {
134
- const context = get_query_context(query);
135
- query = run_after_query(query, async (instance) => {
109
+ query = run_after_query(context.graph.orm, query, async (instance) => {
136
110
  instance[field] = await transform(instance[field], instance, context);
137
111
  return instance;
138
112
  });
@@ -141,118 +115,103 @@ function defineFieldResolver(options = {}) {
141
115
  };
142
116
  }
143
117
 
144
- // src/resolvers/graph.ts
118
+ // ../graphql-orm-resolver/src/resolvers/graph.ts
145
119
  import { parseResolveInfo } from "graphql-parse-resolve-info";
146
- function createGraphResolver(types, options) {
147
- return new GraphResolver(types, options);
148
- }
149
120
  var GraphResolver = class {
150
- constructor(tables, options = {}) {
151
- this.tables = tables;
121
+ constructor(orm2, type_resolvers, options = {}) {
122
+ this.orm = orm2;
123
+ this.type_resolvers = type_resolvers;
152
124
  this.options = options;
153
125
  }
154
126
  resolve(query, { info, context, filters }) {
155
- set_query_context(query, context);
156
127
  const tree = this._get_resolve_tree(info);
157
- return this._resolve_type({ query, tree, filters });
128
+ return this._resolve_type(query, { tree, filters, context });
158
129
  }
159
130
  resolvePage(query, paginator, { info, context, filters }) {
160
- set_query_context(query, context);
161
131
  const tree = this._get_resolve_tree(info);
162
- return this._resolve_page({ query, tree, paginator, filters });
132
+ return this._resolve_page(query, paginator, { tree, filters, context });
163
133
  }
164
134
  _get_resolve_tree(info) {
165
135
  return parseResolveInfo(info);
166
136
  }
167
- _resolve_type({
168
- tree,
169
- query,
170
- filters
171
- }) {
137
+ _resolve_type(query, context) {
138
+ const { tree } = context;
172
139
  const type = Object.keys(tree.fieldsByTypeName)[0];
173
- const table_resolver = this.tables[type];
174
- if (!table_resolver) {
175
- 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}.`);
176
143
  }
177
- return table_resolver({ tree, type, query, filters, graph: this });
144
+ return type_resolver.resolve(query, {
145
+ ...context,
146
+ graph: this,
147
+ tree,
148
+ type
149
+ });
178
150
  }
179
- _resolve_page({
180
- tree,
181
- query,
182
- paginator,
183
- filters
184
- }) {
185
- const { args } = tree;
151
+ _resolve_page(query, paginator, context) {
152
+ let { tree } = context;
186
153
  for (const field of paginator.path) {
187
154
  const type = Object.keys(tree.fieldsByTypeName)[0];
188
155
  tree = tree.fieldsByTypeName[type][field];
189
156
  }
190
- query = this._resolve_type({ query, tree, filters });
191
- return paginator.paginate(query, args);
157
+ query = this._resolve_type(query, { ...context, tree });
158
+ return paginator.paginate(query, {
159
+ ...context,
160
+ graph: this
161
+ });
192
162
  }
193
163
  };
194
164
 
195
- // src/resolvers/page.ts
196
- import { overrideParserInQuery } from "orchid-core";
165
+ // ../graphql-orm-resolver/src/resolvers/page.ts
197
166
  function definePageResolver(paginator, options = {}) {
198
- const { tableField, filters, modify } = options;
199
- return defineFieldResolver({
200
- modify: (query, { field, tree, graph }) => {
201
- let get_page;
202
- query = query.select({
203
- [field]: (q) => {
204
- q = graph._resolve_page({
205
- tree,
206
- query: q[tableField || field],
207
- paginator,
208
- filters
209
- });
210
- get_page = get_query_pagination_handler(q);
211
- return modify ? modify(q, tree) : q;
212
- }
213
- });
214
- overrideParserInQuery(query.query, field, get_page);
215
- return query;
216
- }
217
- });
167
+ const { tableField, modify } = options;
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
+ };
218
186
  }
219
187
 
220
- // src/resolvers/relation.ts
188
+ // ../graphql-orm-resolver/src/resolvers/relation.ts
221
189
  function defineRelationResolver(options = {}) {
222
190
  const { tableField, filters, modify } = options;
223
- return defineFieldResolver({
224
- modify: (query, { field, tree, graph }) => query.select({
225
- [field]: (q) => {
226
- const relation_query = graph._resolve_type({
227
- tree,
228
- query: q[tableField || field],
229
- filters
230
- });
231
- return modify ? modify(relation_query, tree) : relation_query;
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;
232
199
  }
233
- })
234
- });
200
+ });
201
+ };
235
202
  }
236
203
 
237
- // src/utils/is-plain-object.ts
204
+ // ../graphql-orm-resolver/src/utils/is-plain-object.ts
238
205
  function is_plain_object(obj) {
239
206
  return typeof obj === "object" && !Array.isArray(obj);
240
207
  }
241
208
 
242
- // src/filters/filters.ts
243
- function is_scalar(value) {
244
- return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
245
- }
246
- function apply_filters({
247
- query,
248
- filters,
249
- args,
250
- modifiers
251
- }) {
209
+ // ../graphql-orm-resolver/src/filters/filters.ts
210
+ function apply_filters(query, { filters, modifiers, context }) {
252
211
  if (!filters) {
253
212
  return query;
254
213
  }
255
- const filter_obj = args?.filter;
214
+ const filter_obj = context.tree.args?.filter;
256
215
  if (!filter_obj) {
257
216
  return query;
258
217
  }
@@ -264,122 +223,196 @@ function apply_filters({
264
223
  continue;
265
224
  }
266
225
  if (modifiers?.[field]) {
267
- query = modifiers[field](query, value);
226
+ query = modifiers[field](query, value, context);
268
227
  } else {
269
- const [dbfield, op0] = field.split("__");
270
- const op = op0?.toLowerCase() || "exact";
271
- if (
272
- // TODO: add the rest of orchid ops (or allow all of them?)
273
- [
274
- "exact",
275
- "lt",
276
- "lte",
277
- "gt",
278
- "gte",
279
- "like",
280
- "ilike",
281
- "contains"
282
- ].includes(op)
283
- ) {
284
- if (!is_scalar(value)) {
285
- throw new Error(
286
- `Unsupported filter value for ${query.table}.${field}: must be scalar.`
287
- );
288
- }
289
- if (op === "exact") {
290
- query = query.where({ [dbfield]: value });
291
- } else {
292
- const query_op = { like: "contains" }[op] || op;
293
- query = query.where({ [dbfield]: { [query_op]: value } });
294
- }
295
- } else if (op === "in") {
296
- if (!(Array.isArray(value) && value.every(is_scalar))) {
297
- throw new Error(
298
- `Invalid filter value for ${query.table}.${field}: must be an array of scalars.`
299
- );
300
- }
301
- query = query.whereIn(dbfield, value);
302
- } else {
303
- throw new Error(
304
- `Invalid filter ${query.table}.${field}: unsupported operator ${op}.`
305
- );
306
- }
228
+ const [table_field, op0] = field.split("__");
229
+ const op = op0?.toLowerCase();
230
+ query = context.graph.orm.where(query, table_field, op, value);
307
231
  }
308
232
  }
309
233
  return query;
310
234
  }
311
235
 
312
- // src/resolvers/table.ts
313
- function defineTableResolver(table, options = {}) {
314
- const get_default_field_resolver = (field, tableField) => {
315
- const table_field_lookup = tableField || field;
316
- if (options.modifiers?.[table_field_lookup]) {
317
- return (query) => query;
318
- } else if (table.relations?.[table_field_lookup]) {
319
- return defineRelationResolver({ tableField });
320
- } else {
321
- 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
+ }, {});
322
258
  }
323
- };
324
- const { fields } = options;
325
- const table_field_resolvers = fields ? Object.keys(fields).reduce(
326
- (resolvers, field) => {
327
- const r0 = fields[field];
328
- const r = typeof r0 === "function" ? r0 : r0 === true ? get_default_field_resolver(field) : typeof r0 === "string" ? get_default_field_resolver(field, r0) : void 0;
329
- if (r === void 0) {
330
- throw new Error(
331
- `Field resolver must be a function, string, or true; found ${r0}`
332
- );
333
- }
334
- resolvers[field] = r;
335
- return resolvers;
336
- },
337
- {}
338
- ) : void 0;
339
- const { modify, transform } = options;
340
- return function resolve({ tree, type, query, filters, graph }) {
341
- if (query.table !== table.table) {
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;
268
+ const { graph, tree, type, filters } = context;
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) {
342
272
  throw new Error(
343
- `Mismatching query type (expected ${table.table}, found ${query.table}).`
273
+ `Mismatching query table for type ${type}: expected ${table_table}, received ${query_table}.`
344
274
  );
345
275
  }
346
- const allow_all_fields = options.allowAllFields ?? graph.options.allowAllFields ?? table_field_resolvers === void 0;
347
- 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) {
348
278
  throw new Error(
349
- `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.`
350
280
  );
351
281
  }
352
282
  if (modify) {
353
- query = modify(query, tree);
283
+ query = modify(query, context);
354
284
  }
355
285
  for (const subtree of Object.values(tree.fieldsByTypeName[type])) {
356
286
  const field = subtree.name;
357
- 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);
358
288
  if (!r) {
359
- throw new Error(`No field resolver defined for field ${type}.${field}`);
289
+ throw new Error(`No field resolver defined for field ${type}.${field}.`);
360
290
  }
361
291
  if (r) {
362
- query = r(query, { field, tree: subtree, graph });
292
+ query = r(query, { ...context, tree: subtree, field });
363
293
  }
364
294
  }
365
- const allow_all_filters = options.allowAllFilters ?? graph.options.allowAllFilters;
295
+ const allow_all_filters = this.options.allowAllFilters ?? graph.options.allowAllFilters;
366
296
  const effective_filters = allow_all_filters ? true : filters;
367
297
  if (effective_filters) {
368
- query = apply_filters({
369
- query,
298
+ query = apply_filters(query, {
370
299
  filters: effective_filters,
371
- args: tree.args,
372
- modifiers: options.modifiers
300
+ modifiers: this.modifiers,
301
+ context
373
302
  });
374
303
  }
375
304
  if (transform) {
376
- const context = get_query_context(query);
377
- query = run_after_query(query, (instance) => {
305
+ query = run_after_query(this.orm, query, (instance) => {
378
306
  return transform(instance, context);
379
307
  });
380
308
  }
309
+ query = this.orm.prevent_select_all(query);
381
310
  return query;
382
- };
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);
383
416
  }
384
417
  export {
385
418
  createGraphResolver,