@snowtop/ent 0.2.0-alpha.6.test → 0.2.0-alpha.7

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.
@@ -27,59 +27,34 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.BaseEdgeQuery = void 0;
30
- const ent_1 = require("../ent");
31
- const clause = __importStar(require("../clause"));
32
30
  const memoizee_1 = __importDefault(require("memoizee"));
31
+ const types_1 = require("util/types");
32
+ const clause = __importStar(require("../clause"));
33
+ const ent_1 = require("../ent");
33
34
  const privacy_1 = require("../privacy");
34
- const uuid_1 = require("uuid");
35
35
  const query_impl_1 = require("../query_impl");
36
- const types_1 = require("util/types");
37
36
  // TODO can we generalize EdgeQuery to support any clause
38
37
  function assertPositive(n) {
39
38
  if (n < 0) {
40
39
  throw new Error("cannot use a negative number");
41
40
  }
42
41
  }
43
- function convertToIntMaybe(val) {
44
- // TODO handle both cases... (time vs not) better
45
- // TODO change this to only do the parseInt part if time...
46
- // pass flag indicating if time?
47
- // handle non-integers for which the first part is an int
48
- // @ts-ignore
49
- if (isNaN(val)) {
50
- return val;
42
+ function translateCursorToKeyValues(cursor, opts) {
43
+ const { keys } = opts;
44
+ const decoded = atob(cursor);
45
+ let cursorData = [];
46
+ try {
47
+ cursorData = JSON.parse(decoded);
51
48
  }
52
- const time = parseInt(val, 10);
53
- if (isNaN(time)) {
54
- return val;
49
+ catch (error) {
50
+ throw new Error(`Bad cursor format ${cursor} passed`);
55
51
  }
56
- return time;
57
- }
58
- function assertValidCursor(cursor, opts) {
59
- let decoded = Buffer.from(cursor, "base64").toString("ascii");
60
- let parts = decoded.split(":");
61
- const { keys } = opts;
62
52
  // invalid or unknown cursor. nothing to do here.
63
- // we should have the same number of parts as keys * 2
64
- if (parts.length !== keys.length * 2) {
53
+ // we should have the same number of parts as keys
54
+ if (cursorData.length !== keys.length) {
65
55
  throw new Error(`invalid cursor ${cursor} passed`);
66
56
  }
67
- const values = [];
68
- for (let i = 0; i < keys.length; i++) {
69
- const key = keys[i];
70
- const keyPart = parts[i * 2];
71
- if (key !== keyPart) {
72
- throw new Error(`invalid cursor ${cursor} passed. expected ${key}. got ${keyPart} as key of field`);
73
- }
74
- const val = parts[i * 2 + 1];
75
- // uuid, don't parse int since it tries to validate just first part
76
- if ((0, uuid_1.validate)(val)) {
77
- values.push(val);
78
- continue;
79
- }
80
- values.push(convertToIntMaybe(val));
81
- }
82
- return values;
57
+ return cursorData;
83
58
  }
84
59
  const orderbyRegex = new RegExp(/([0-9a-z_]+)[ ]?([0-9a-z_]+)?/i);
85
60
  class FirstFilter {
@@ -87,14 +62,14 @@ class FirstFilter {
87
62
  this.options = options;
88
63
  this.pageMap = new Map();
89
64
  this.usedQuery = false;
90
- this.cursorValues = [];
65
+ this.cursorKeyValues = [];
91
66
  assertPositive(options.limit);
92
- this.sortCol = options.sortCol;
93
67
  if (options.after) {
94
- this.cursorValues = assertValidCursor(options.after, {
68
+ this.cursorKeyValues = translateCursorToKeyValues(options.after, {
95
69
  keys: options.cursorKeys,
96
70
  });
97
- this.offset = this.cursorValues[0];
71
+ this.offset =
72
+ this.cursorKeyValues[this.cursorKeyValues.length - 1][1] ?? undefined;
98
73
  }
99
74
  this.edgeQuery = options.query;
100
75
  }
@@ -158,58 +133,25 @@ class FirstFilter {
158
133
  // so we'd need a way to indicate whether this is done in sql or not
159
134
  return edges;
160
135
  }
161
- getOffsetForQuery() {
162
- // cursorCol maps to offset which we get from the cursor in assertValidCursor
163
- return this.options.cursorColIsDate
164
- ? new Date(this.offset).toISOString()
165
- : this.offset;
166
- }
167
- getSortValueForQuery() {
168
- // sortCol maps to the value we're comparing against
169
- return this.options.sortColIsDate
170
- ? new Date(this.cursorValues[1]).toISOString()
171
- : this.cursorValues[1];
172
- }
173
136
  async query(options) {
174
137
  this.usedQuery = true;
175
138
  // we fetch an extra one to see if we're at the end
176
- const limit = this.options.limit + 1;
177
- options.limit = limit;
178
- // we sort by most recent first
179
- // so when paging, we fetch afterCursor X
180
- const less = this.options.orderby[0].direction === "DESC";
181
- const orderby = this.options.orderby;
182
- if (this.options.cursorCol !== this.sortCol) {
183
- // we also sort cursor col in same direction. (direction doesn't matter)
184
- orderby.push({
185
- column: this.options.cursorCol,
186
- direction: orderby[0].direction,
187
- });
188
- if (this.offset) {
189
- const res = this.edgeQuery.getTableName();
190
- let tableName = (0, types_1.isPromise)(res) ? await res : res;
191
- // using a join, we already know sortCol and cursorCol are different
192
- // we have encoded both values in the cursor
193
- // includeSortColInCursor() is true in this case
194
- if (this.cursorValues.length === 2 &&
195
- this.options.cursorKeys.length === 2) {
196
- options.clause = clause.AndOptional(options.clause, clause.PaginationMultipleColsQuery(this.sortCol, this.options.cursorCol, less, this.getSortValueForQuery(), this.getOffsetForQuery(), this.options.fieldOptions?.fieldsAlias ??
197
- this.options.fieldOptions?.alias));
198
- }
199
- else {
200
- // inner col time
201
- options.clause = clause.AndOptional(options.clause, clause.PaginationMultipleColsSubQuery(this.sortCol, less ? "<" : ">", tableName, this.options.cursorCol, this.getOffsetForQuery()));
202
- }
203
- }
204
- }
205
- else {
206
- if (this.offset) {
207
- const clauseFn = less ? clause.Less : clause.Greater;
208
- const val = this.getOffsetForQuery();
209
- options.clause = clause.AndOptional(options.clause, clauseFn(this.sortCol, val));
139
+ options.limit = this.options.limit + 1;
140
+ const orderBy = this.options.orderby;
141
+ if (this.offset) {
142
+ const keyValuePairs = {};
143
+ for (const [key, value] of this.cursorKeyValues) {
144
+ keyValuePairs[key] = value;
210
145
  }
146
+ options.clause = clause.AndOptional(options.clause, clause.PaginationUnboundColsQuery(orderBy.map((orderBy) => ({
147
+ sortCol: orderBy.column,
148
+ sortValue: keyValuePairs[orderBy.column],
149
+ direction: orderBy.direction,
150
+ nullsPlacement: orderBy.nullsPlacement,
151
+ overrideAlias: orderBy.alias,
152
+ }))));
211
153
  }
212
- options.orderby = orderby;
154
+ options.orderby = orderBy;
213
155
  return options;
214
156
  }
215
157
  // TODO?
@@ -223,14 +165,14 @@ class LastFilter {
223
165
  constructor(options) {
224
166
  this.options = options;
225
167
  this.pageMap = new Map();
226
- this.cursorValues = [];
168
+ this.cursorKeyValues = [];
227
169
  assertPositive(options.limit);
228
- this.sortCol = options.sortCol;
229
170
  if (options.before) {
230
- this.cursorValues = assertValidCursor(options.before, {
171
+ this.cursorKeyValues = translateCursorToKeyValues(options.before, {
231
172
  keys: options.cursorKeys,
232
173
  });
233
- this.offset = this.cursorValues[0];
174
+ this.offset =
175
+ this.cursorKeyValues[this.cursorKeyValues.length - 1][1] ?? undefined;
234
176
  }
235
177
  this.edgeQuery = options.query;
236
178
  }
@@ -261,56 +203,25 @@ class LastFilter {
261
203
  }
262
204
  return ret;
263
205
  }
264
- // copied from FirstFilter
265
- getOffsetForQuery() {
266
- // cursorCol maps to offset which we get from the cursor in assertValidCursor
267
- return this.options.cursorColIsDate
268
- ? new Date(this.offset).toISOString()
269
- : this.offset;
270
- }
271
- getSortValueForQuery() {
272
- // sortCol maps to the value we're comparing against
273
- return this.options.sortColIsDate
274
- ? new Date(this.cursorValues[1]).toISOString()
275
- : this.cursorValues[1];
276
- }
277
206
  async query(options) {
278
- const orderby = (0, query_impl_1.reverseOrderBy)(this.options.orderby);
279
- const greater = orderby[0].direction === "ASC";
280
- options.limit = this.options.limit + 1; // fetch an extra so we know if previous pag
281
- if (this.options.cursorCol !== this.sortCol) {
282
- const res = this.edgeQuery.getTableName();
283
- const tableName = (0, types_1.isPromise)(res) ? await res : res;
284
- if (this.offset) {
285
- // using a join, we already know sortCol and cursorCol are different
286
- // we have encoded both values in the cursor
287
- // includeSortColInCursor() is true in this case
288
- if (this.cursorValues.length === 2 &&
289
- this.options.cursorKeys.length === 2) {
290
- options.clause = clause.AndOptional(options.clause, clause.PaginationMultipleColsQuery(this.sortCol, this.options.cursorCol,
291
- // flipped here since we're going in the opposite direction
292
- !greater, this.getSortValueForQuery(), this.getOffsetForQuery(), this.options.fieldOptions?.fieldsAlias ??
293
- this.options.fieldOptions?.alias));
294
- }
295
- else {
296
- // inner col time
297
- options.clause = clause.AndOptional(options.clause, clause.PaginationMultipleColsSubQuery(this.sortCol, greater ? ">" : "<", tableName, this.options.cursorCol, this.getOffsetForQuery()));
298
- }
299
- }
300
- // we also sort cursor col in same direction. (direction doesn't matter)
301
- orderby.push({
302
- column: this.options.cursorCol,
303
- direction: orderby[0].direction,
304
- });
305
- }
306
- else {
307
- if (this.offset) {
308
- const clauseFn = greater ? clause.Greater : clause.Less;
309
- const val = this.getOffsetForQuery();
310
- options.clause = clause.AndOptional(options.clause, clauseFn(this.sortCol, val));
207
+ // we fetch an extra one to see if we're at the end
208
+ options.limit = this.options.limit + 1;
209
+ // we also sort cursor col in same direction. (direction doesn't matter)
210
+ const orderBy = (0, query_impl_1.reverseOrderBy)(this.options.orderby);
211
+ if (this.offset) {
212
+ const keyValuePairs = {};
213
+ for (const [key, value] of this.cursorKeyValues) {
214
+ keyValuePairs[key] = value;
311
215
  }
216
+ options.clause = clause.AndOptional(options.clause, clause.PaginationUnboundColsQuery(orderBy.map((orderBy) => ({
217
+ sortCol: orderBy.column,
218
+ sortValue: keyValuePairs[orderBy.column],
219
+ direction: orderBy.direction,
220
+ nullsPlacement: orderBy.nullsPlacement,
221
+ overrideAlias: orderBy.alias,
222
+ }))));
312
223
  }
313
- options.orderby = orderby;
224
+ options.orderby = orderBy;
314
225
  return options;
315
226
  }
316
227
  paginationInfo(id) {
@@ -318,7 +229,7 @@ class LastFilter {
318
229
  }
319
230
  }
320
231
  class BaseEdgeQuery {
321
- constructor(viewer, sortColOrOptions, cursorColMaybe) {
232
+ constructor(viewer, options) {
322
233
  this.viewer = viewer;
323
234
  this.filters = [];
324
235
  this.edges = new Map();
@@ -377,57 +288,29 @@ class BaseEdgeQuery {
377
288
  await Promise.all(promises);
378
289
  return results;
379
290
  };
380
- let sortCol;
381
- let cursorCol;
382
- let sortColIsDate = false;
383
- let cursorColIsDate = false;
384
- if (typeof sortColOrOptions === "string") {
385
- sortCol = sortColOrOptions;
386
- cursorCol = cursorColMaybe;
387
- this.edgeQueryOptions = {
388
- cursorCol,
389
- orderby: [
390
- {
391
- column: sortCol,
392
- direction: "DESC",
393
- },
394
- ],
395
- };
396
- }
397
- else {
398
- if (typeof sortColOrOptions.orderby === "string") {
399
- sortCol = sortColOrOptions.orderby;
400
- }
401
- else {
402
- // TODO this orderby isn't consistent and this logic needs to be changed anywhere that's using this and this.getSortCol()
403
- sortCol = sortColOrOptions.orderby[0].column;
404
- sortColIsDate = sortColOrOptions.orderby[0].dateColumn ?? false;
405
- }
406
- cursorCol = sortColOrOptions.cursorCol;
407
- cursorColIsDate = sortColOrOptions.cursorColIsDate ?? false;
408
- this.edgeQueryOptions = sortColOrOptions;
409
- }
410
- this.sortCol = sortCol;
411
- this.sortColIsDate = sortColIsDate;
412
- this.cursorColIsDate = cursorColIsDate;
413
- let m = orderbyRegex.exec(sortCol);
414
- if (!m) {
415
- throw new Error(`invalid sort column ${sortCol}`);
416
- }
417
- this.sortCol = m[1];
418
- if (m[2]) {
419
- throw new Error(`passing direction in sort column is not supproted. use orderby`);
291
+ // we also sort cursor col in same direction. (direction doesn't matter)
292
+ const orderBy = [...options.orderby];
293
+ if (!options.orderby.some((orderBy) => orderBy.column === options.cursorCol)) {
294
+ orderBy.push({
295
+ column: options.cursorCol,
296
+ direction: orderBy?.[0].direction || "DESC",
297
+ });
420
298
  }
421
- this.cursorCol = cursorCol;
299
+ orderBy.forEach((o) => {
300
+ o.alias =
301
+ o.alias ??
302
+ (options.fieldOptions?.disableFieldsAlias
303
+ ? undefined
304
+ : (options.fieldOptions?.fieldsAlias ?? options.fieldOptions?.alias));
305
+ });
306
+ this.edgeQueryOptions = { ...options, orderby: orderBy };
307
+ this.cursorCol = options.cursorCol;
308
+ this.cursorKeys = orderBy.map((orderBy) => orderBy.column);
422
309
  this.memoizedloadEdges = (0, memoizee_1.default)(this.loadEdges.bind(this));
423
310
  this.genIDInfosToFetch = (0, memoizee_1.default)(this.genIDInfosToFetchImpl.bind(this));
424
- this.cursorKeys.push(this.cursorCol);
425
- if (this.includeSortColInCursor(this.edgeQueryOptions)) {
426
- this.cursorKeys.push(this.sortCol);
427
- }
428
311
  }
429
- getSortCol() {
430
- return this.sortCol;
312
+ getCursorCol() {
313
+ return this.cursorCol;
431
314
  }
432
315
  getPrivacyPolicy() {
433
316
  // default PrivacyPolicy is always allow. nothing to do here
@@ -439,11 +322,8 @@ class BaseEdgeQuery {
439
322
  this.filters.push(new FirstFilter({
440
323
  limit: n,
441
324
  after,
442
- sortCol: this.sortCol,
443
325
  cursorCol: this.cursorCol,
444
326
  cursorKeys: this.cursorKeys,
445
- cursorColIsDate: this.cursorColIsDate,
446
- sortColIsDate: this.sortColIsDate,
447
327
  orderby: this.edgeQueryOptions.orderby,
448
328
  query: this,
449
329
  fieldOptions: this.edgeQueryOptions.fieldOptions,
@@ -466,11 +346,8 @@ class BaseEdgeQuery {
466
346
  this.filters.push(new LastFilter({
467
347
  limit: n,
468
348
  before,
469
- sortCol: this.sortCol,
470
349
  cursorCol: this.cursorCol,
471
350
  cursorKeys: this.cursorKeys,
472
- cursorColIsDate: this.cursorColIsDate,
473
- sortColIsDate: this.sortColIsDate,
474
351
  orderby: this.edgeQueryOptions.orderby,
475
352
  query: this,
476
353
  fieldOptions: this.edgeQueryOptions.fieldOptions,
@@ -583,13 +460,10 @@ class BaseEdgeQuery {
583
460
  this.queryDispatched = true;
584
461
  return this.edges;
585
462
  }
586
- includeSortColInCursor(options) {
587
- return false;
588
- }
589
463
  getCursor(row) {
590
464
  return (0, ent_1.getCursor)({
591
465
  row,
592
- keys: this.cursorKeys,
466
+ cursorKeys: this.cursorKeys,
593
467
  });
594
468
  }
595
469
  }
@@ -1,10 +1,10 @@
1
- import { Data, ID, Viewer } from "../base";
2
- import { FakeUser, FakeContact } from "../../testutils/fake_data/index";
3
- import { EdgeQuery } from "./query";
4
1
  import { BuilderSchema } from "../../testutils/builder";
2
+ import { FakeContact, FakeUser } from "../../testutils/fake_data/index";
5
3
  import { MockLogs } from "../../testutils/mock_log";
4
+ import { Data, ID, Viewer } from "../base";
6
5
  import { Clause } from "../clause";
7
6
  import { OrderBy } from "../query_impl";
7
+ import { EdgeQuery } from "./query";
8
8
  interface options<TData extends Data> {
9
9
  newQuery: (v: Viewer, user: FakeUser) => EdgeQuery<FakeUser, FakeContact, TData>;
10
10
  tableName: string;
@@ -1,20 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.commonTests = void 0;
4
- const ent_1 = require("../ent");
5
- const global_schema_1 = require("../global_schema");
6
- const viewer_1 = require("../viewer");
4
+ const action_1 = require("../../action");
5
+ const builder_1 = require("../../testutils/builder");
6
+ const test_context_1 = require("../../testutils/context/test_context");
7
+ const temp_db_1 = require("../../testutils/db/temp_db");
7
8
  const index_1 = require("../../testutils/fake_data/index");
8
9
  const test_helpers_1 = require("../../testutils/fake_data/test_helpers");
9
- const temp_db_1 = require("../../testutils/db/temp_db");
10
- const test_context_1 = require("../../testutils/context/test_context");
11
- const logger_1 = require("../logger");
10
+ const query_1 = require("../../testutils/query");
12
11
  const test_edge_global_schema_1 = require("../../testutils/test_edge_global_schema");
13
- const builder_1 = require("../../testutils/builder");
14
- const action_1 = require("../../action");
15
12
  const clause_1 = require("../clause");
13
+ const ent_1 = require("../ent");
14
+ const global_schema_1 = require("../global_schema");
15
+ const logger_1 = require("../logger");
16
16
  const query_impl_1 = require("../query_impl");
17
- const query_1 = require("../../testutils/query");
17
+ const viewer_1 = require("../viewer");
18
18
  const commonTests = (opts) => {
19
19
  (0, logger_1.setLogLevels)(["query", "error"]);
20
20
  const ml = opts.ml;
@@ -154,7 +154,7 @@ const commonTests = (opts) => {
154
154
  let expLimit = disablePaginationBump ? limit : limit + 1;
155
155
  expect(whereClause, `${i}`).toBe(
156
156
  // default limit
157
- `${opts.clause.clause(1)} ORDER BY ${(0, query_impl_1.getOrderByPhrase)(orderby)}, ${uniqCol} ${orderby[0].direction} LIMIT ${expLimit}`);
157
+ `${opts.clause.clause(1)} ORDER BY ${(0, query_impl_1.getOrderByPhrase)(orderby)} LIMIT ${expLimit}`);
158
158
  }
159
159
  }
160
160
  function verifyCountQuery({ length = 1, numQueries = 1 }) {
@@ -167,40 +167,47 @@ const commonTests = (opts) => {
167
167
  function verifyFirstAfterCursorQuery(filter, length = 1, limit = 3) {
168
168
  // cache showing up in a few because of cross runs...
169
169
  expect(ml.logs.length).toBeGreaterThanOrEqual(length);
170
- const uniqCol = isCustomQuery(filter) ? "id" : "id2";
171
170
  let parts = opts.clause.clause(1).split(" AND ");
172
- const cmp = (0, clause_1.PaginationMultipleColsSubQuery)(opts.orderby[0].column, opts.orderby[0].direction === "DESC" ? "<" : ">", opts.tableName, uniqCol, "").clause(opts.clause.values().length + 1);
171
+ const cmp = (0, clause_1.PaginationUnboundColsQuery)(opts.orderby.map((orderBy) => ({
172
+ direction: orderBy.direction,
173
+ sortCol: orderBy.column,
174
+ sortValue: "_",
175
+ }))).clause(opts.clause.values().length + 1);
173
176
  // order of parts is different in custom query seemingly
174
177
  const customQuery = isCustomQuery(filter);
175
178
  if (!customQuery && parts[parts.length - 1] === "deleted_at IS NULL") {
176
179
  parts = parts
177
180
  .slice(0, parts.length - 1)
178
- .concat([cmp, "deleted_at IS NULL"]);
181
+ .concat([`(${cmp})`, "deleted_at IS NULL"]);
179
182
  }
180
183
  else {
181
- parts.push(cmp);
184
+ parts.push(`(${cmp})`);
182
185
  }
183
- expect((0, query_1.getWhereClause)(ml.logs[0])).toBe(`${parts.join(" AND ")} ORDER BY ${(0, query_impl_1.getOrderByPhrase)(opts.orderby)}, ${uniqCol} ${opts.orderby[0].direction} LIMIT ${limit + 1}`);
186
+ expect((0, query_1.getWhereClause)(ml.logs[0])).toBe(`${parts.join(" AND ")} ORDER BY ${(0, query_impl_1.getOrderByPhrase)(opts.orderby)} LIMIT ${limit + 1}`);
184
187
  }
185
188
  function verifyLastBeforeCursorQuery(filter, { length = 1, limit = 3, orderby = opts.orderby }) {
186
189
  // cache showing up in a few because of cross runs...
187
190
  expect(ml.logs.length).toBeGreaterThanOrEqual(length);
188
- const uniqCol = isCustomQuery(filter) ? "id" : "id2";
189
191
  let parts = opts.clause.clause(1).split(" AND ");
190
- const cmp = (0, clause_1.PaginationMultipleColsSubQuery)(orderby[0].column, orderby[0].direction === "ASC" ? ">" : "<", opts.tableName, uniqCol, "").clause(opts.clause.values().length + 1);
192
+ const cmp = (0, clause_1.PaginationUnboundColsQuery)(opts.orderby.map((orderBy) => ({
193
+ // Reverse order for "last" cursor
194
+ direction: orderBy.direction === "DESC" ? "ASC" : "DESC",
195
+ sortCol: orderBy.column,
196
+ sortValue: "_",
197
+ }))).clause(opts.clause.values().length + 1);
191
198
  // order of parts is different in custom query seemingly
192
199
  const customQuery = isCustomQuery(filter);
193
200
  if (!customQuery && parts[parts.length - 1] === "deleted_at IS NULL") {
194
201
  parts = parts
195
202
  .slice(0, parts.length - 1)
196
- .concat([cmp, "deleted_at IS NULL"]);
203
+ .concat([`(${cmp})`, "deleted_at IS NULL"]);
197
204
  }
198
205
  else {
199
- parts.push(cmp);
206
+ parts.push(`(${cmp})`);
200
207
  }
201
208
  expect((0, query_1.getWhereClause)(ml.logs[0])).toBe(
202
209
  // extra fetched for pagination
203
- `${parts.join(" AND ")} ORDER BY ${(0, query_impl_1.getOrderByPhrase)(orderby)}, ${uniqCol} ${orderby[0].direction} LIMIT ${limit + 1}`);
210
+ `${parts.join(" AND ")} ORDER BY ${(0, query_impl_1.getOrderByPhrase)(orderby)} LIMIT ${limit + 1}`);
204
211
  }
205
212
  function getViewer() {
206
213
  return new viewer_1.LoggedOutViewer();
@@ -210,7 +217,8 @@ const commonTests = (opts) => {
210
217
  if (isCustomQuery(q)) {
211
218
  opts = {
212
219
  row: contacts[idx],
213
- keys: ["id"],
220
+ cursorKeys: ["created_at", "id"],
221
+ rowKeys: ["createdAt", "id"],
214
222
  };
215
223
  }
216
224
  else {
@@ -218,8 +226,8 @@ const commonTests = (opts) => {
218
226
  // is from assoc_edge table id2 field and so cursor takes it from there
219
227
  opts = {
220
228
  row: contacts[idx],
221
- keys: ["id2"],
222
- cursorKeys: ["id"],
229
+ cursorKeys: ["time", "id2"],
230
+ rowKeys: ["createdAt", "id"],
223
231
  };
224
232
  }
225
233
  return (0, ent_1.getCursor)(opts);
@@ -4,7 +4,6 @@ export interface OrderByOption {
4
4
  direction: "ASC" | "DESC";
5
5
  alias?: string;
6
6
  nullsPlacement?: "first" | "last";
7
- dateColumn?: boolean;
8
7
  }
9
8
  export type OrderBy = OrderByOption[];
10
9
  export declare function getOrderByPhrase(orderby: OrderBy, alias?: string): string;
@@ -1,18 +1,19 @@
1
- import { EdgeQuery, PaginationInfo } from "../../core/query/query";
2
1
  import { Data, Ent, ID, Viewer } from "../../core/base";
2
+ import { EdgeQuery, PaginationInfo } from "../../core/query/query";
3
3
  export interface GraphQLEdge<T extends Data> {
4
4
  edge: T;
5
5
  node: Ent;
6
6
  cursor: string;
7
7
  }
8
+ type MaybePromise<T> = T | Promise<T>;
8
9
  interface edgeQueryCtr<T extends Ent, TEdge extends Data, TViewer extends Viewer> {
9
- (v: TViewer, src: T): EdgeQuery<T, Ent, TEdge>;
10
+ (v: TViewer, src: T): MaybePromise<EdgeQuery<T, Ent, TEdge>>;
10
11
  }
11
12
  interface edgeQueryCtr2<T extends Ent, TEdge extends Data, TViewer extends Viewer> {
12
- (v: TViewer): EdgeQuery<T, Ent, TEdge>;
13
+ (v: TViewer): MaybePromise<EdgeQuery<T, Ent, TEdge>>;
13
14
  }
14
15
  export declare class GraphQLEdgeConnection<TSource extends Ent, TEdge extends Data, TViewer extends Viewer = Viewer> {
15
- query: EdgeQuery<TSource, Ent, TEdge>;
16
+ query: Promise<EdgeQuery<TSource, Ent, TEdge>>;
16
17
  private results;
17
18
  private viewer;
18
19
  private source?;
@@ -7,13 +7,13 @@ class GraphQLEdgeConnection {
7
7
  this.results = [];
8
8
  this.viewer = viewer;
9
9
  if (typeof arg2 === "function") {
10
- this.query = arg2(this.viewer);
10
+ this.query = Promise.resolve(arg2(this.viewer));
11
11
  }
12
12
  else {
13
13
  this.source = arg2;
14
14
  }
15
15
  if (typeof arg3 === "function") {
16
- this.query = arg3(this.viewer, this.source);
16
+ this.query = Promise.resolve(arg3(this.viewer, this.source));
17
17
  }
18
18
  else {
19
19
  this.args = arg3;
@@ -29,27 +29,31 @@ class GraphQLEdgeConnection {
29
29
  throw new Error("cannot process before without last");
30
30
  }
31
31
  if (this.args.first) {
32
- this.query = this.query.first(this.args.first, this.args.after);
32
+ const argFirst = this.args.first;
33
+ const argAfter = this.args.after;
34
+ this.query = this.query.then((query) => query.first(argFirst, argAfter));
33
35
  }
34
36
  if (this.args.last) {
35
- this.query = this.query.last(this.args.last, this.args.cursor);
37
+ const argLast = this.args.last;
38
+ const argBefore = this.args.before;
39
+ this.query = this.query.then((query) => query.last(argLast, argBefore));
36
40
  }
37
41
  // TODO custom args
38
42
  // how to proceed
39
43
  }
40
44
  }
41
45
  first(limit, cursor) {
42
- this.query = this.query.first(limit, cursor);
46
+ this.query = this.query.then((query) => query.first(limit, cursor));
43
47
  }
44
48
  last(limit, cursor) {
45
- this.query = this.query.last(limit, cursor);
49
+ this.query = this.query.then((query) => query.last(limit, cursor));
46
50
  }
47
51
  // any custom filters can be applied here...
48
52
  modifyQuery(fn) {
49
- this.query = fn(this.query);
53
+ this.query = this.query.then((query) => fn(query));
50
54
  }
51
55
  async queryTotalCount() {
52
- return this.query.queryRawCount();
56
+ return (await this.query).queryRawCount();
53
57
  }
54
58
  async queryEdges() {
55
59
  // because of privacy, we need to query the node regardless of if the node is there
@@ -60,7 +64,7 @@ class GraphQLEdgeConnection {
60
64
  // if nodes queried just return ents
61
65
  // unlikely to query nodes and pageInfo so we just load this separately for now
62
66
  async queryNodes() {
63
- return this.query.queryEnts();
67
+ return (await this.query).queryEnts();
64
68
  }
65
69
  defaultPageInfo() {
66
70
  return {
@@ -72,7 +76,7 @@ class GraphQLEdgeConnection {
72
76
  }
73
77
  async queryPageInfo() {
74
78
  await this.queryData();
75
- const paginationInfo = this.query.paginationInfo();
79
+ const paginationInfo = (await this.query).paginationInfo();
76
80
  if (this.source !== undefined) {
77
81
  return paginationInfo.get(this.source.id) || this.defaultPageInfo();
78
82
  }
@@ -85,24 +89,25 @@ class GraphQLEdgeConnection {
85
89
  return this.defaultPageInfo();
86
90
  }
87
91
  async queryData() {
92
+ const query = await this.query;
88
93
  const [edges, ents] = await Promise.all([
89
94
  // TODO need a test that this will only fetch edges once
90
95
  // and then fetch ents afterward
91
- this.query.queryEdges(),
92
- this.query.queryEnts(),
96
+ query.queryEdges(),
97
+ query.queryEnts(),
93
98
  ]);
94
99
  let entsMap = new Map();
95
100
  ents.forEach((ent) => entsMap.set(ent.id, ent));
96
101
  let results = [];
97
102
  for (const edge of edges) {
98
- const node = entsMap.get(this.query.dataToID(edge));
103
+ const node = entsMap.get(query.dataToID(edge));
99
104
  if (!node) {
100
105
  continue;
101
106
  }
102
107
  results.push({
103
108
  edge,
104
109
  node,
105
- cursor: this.query.getCursor(edge),
110
+ cursor: query.getCursor(edge),
106
111
  });
107
112
  }
108
113
  this.results = results;
@@ -1,6 +1,6 @@
1
- import { Viewer, Data, Ent } from "../../core/base";
2
- import { FakeContact } from "../../testutils/fake_data/index";
1
+ import { Data, Ent, Viewer } from "../../core/base";
3
2
  import { EdgeQuery } from "../../core/query/query";
3
+ import { FakeContact } from "../../testutils/fake_data/index";
4
4
  interface options<TEnt extends Ent, TEdge extends Data> {
5
5
  getQuery: (v: Viewer, src: Ent) => EdgeQuery<TEnt, Ent, TEdge>;
6
6
  tableName: string;