@subsquid/openreader 2.1.0 → 3.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.
Files changed (72) hide show
  1. package/lib/ir/args.d.ts +1 -1
  2. package/lib/ir/args.d.ts.map +1 -1
  3. package/lib/ir/connection.d.ts +3 -4
  4. package/lib/ir/connection.d.ts.map +1 -1
  5. package/lib/ir/connection.js.map +1 -1
  6. package/lib/ir/fields.d.ts +6 -2
  7. package/lib/ir/fields.d.ts.map +1 -1
  8. package/lib/ir/fields.js +15 -0
  9. package/lib/ir/fields.js.map +1 -1
  10. package/lib/limit.size.d.ts +4 -4
  11. package/lib/limit.size.d.ts.map +1 -1
  12. package/lib/limit.size.js +51 -20
  13. package/lib/limit.size.js.map +1 -1
  14. package/lib/main.js +0 -9
  15. package/lib/main.js.map +1 -1
  16. package/lib/model.d.ts +2 -1
  17. package/lib/model.d.ts.map +1 -1
  18. package/lib/model.schema.d.ts +2 -2
  19. package/lib/model.schema.d.ts.map +1 -1
  20. package/lib/model.schema.js +8 -3
  21. package/lib/model.schema.js.map +1 -1
  22. package/lib/model.tools.d.ts +6 -1
  23. package/lib/model.tools.d.ts.map +1 -1
  24. package/lib/model.tools.js +111 -8
  25. package/lib/model.tools.js.map +1 -1
  26. package/lib/opencrud/orderBy.d.ts +2 -2
  27. package/lib/opencrud/orderBy.d.ts.map +1 -1
  28. package/lib/opencrud/orderBy.js +13 -17
  29. package/lib/opencrud/orderBy.js.map +1 -1
  30. package/lib/opencrud/schema.d.ts +4 -4
  31. package/lib/opencrud/schema.d.ts.map +1 -1
  32. package/lib/opencrud/schema.js +53 -62
  33. package/lib/opencrud/schema.js.map +1 -1
  34. package/lib/opencrud/tree.d.ts +9 -7
  35. package/lib/opencrud/tree.d.ts.map +1 -1
  36. package/lib/opencrud/tree.js +32 -14
  37. package/lib/opencrud/tree.js.map +1 -1
  38. package/lib/sql/cursor.js +2 -2
  39. package/lib/sql/cursor.js.map +1 -1
  40. package/lib/sql/mapping.d.ts +3 -1
  41. package/lib/sql/mapping.d.ts.map +1 -1
  42. package/lib/sql/mapping.js +16 -1
  43. package/lib/sql/mapping.js.map +1 -1
  44. package/lib/sql/printer.d.ts +29 -11
  45. package/lib/sql/printer.d.ts.map +1 -1
  46. package/lib/sql/printer.js +106 -10
  47. package/lib/sql/printer.js.map +1 -1
  48. package/lib/sql/query.d.ts +11 -11
  49. package/lib/sql/query.d.ts.map +1 -1
  50. package/lib/sql/query.js +41 -19
  51. package/lib/sql/query.js.map +1 -1
  52. package/lib/test/queryable.test.d.ts +2 -0
  53. package/lib/test/queryable.test.d.ts.map +1 -0
  54. package/lib/test/queryable.test.js +255 -0
  55. package/lib/test/queryable.test.js.map +1 -0
  56. package/package.json +2 -2
  57. package/src/ir/args.ts +1 -1
  58. package/src/ir/connection.ts +3 -4
  59. package/src/ir/fields.ts +22 -2
  60. package/src/limit.size.ts +55 -30
  61. package/src/main.ts +0 -11
  62. package/src/model.schema.ts +16 -9
  63. package/src/model.tools.ts +121 -8
  64. package/src/model.ts +2 -1
  65. package/src/opencrud/orderBy.ts +13 -17
  66. package/src/opencrud/schema.ts +78 -84
  67. package/src/opencrud/tree.ts +55 -26
  68. package/src/sql/cursor.ts +2 -2
  69. package/src/sql/mapping.ts +18 -1
  70. package/src/sql/printer.ts +137 -21
  71. package/src/sql/query.ts +50 -30
  72. package/src/test/queryable.test.ts +258 -0
@@ -1,14 +1,15 @@
1
- import {unexpectedCase} from "@subsquid/util-internal"
2
- import assert from "assert"
3
- import {Dialect} from "../dialect"
4
- import {EntityListArguments, OrderBy, Where} from "../ir/args"
5
- import {FieldRequest} from "../ir/fields"
6
- import {Model} from "../model"
7
- import {Cursor, EntityCursor} from "./cursor"
8
- import {AliasSet, ColumnSet, escapeIdentifier, JoinSet, printClause} from "./util"
1
+ import {unexpectedCase} from '@subsquid/util-internal'
2
+ import assert from 'assert'
3
+ import {Dialect} from '../dialect'
4
+ import {OrderBy, SortOrder, SqlArguments, Where} from '../ir/args'
5
+ import {FieldRequest, FieldsByEntity} from '../ir/fields'
6
+ import {Model} from '../model'
7
+ import {getQueryableEntities} from '../model.tools'
8
+ import {Cursor, EntityCursor} from './cursor'
9
+ import {AliasSet, ColumnSet, escapeIdentifier, JoinSet, printClause} from './util'
9
10
 
10
11
 
11
- export class EntityListQueryPrinter {
12
+ export class EntitySqlPrinter {
12
13
  private aliases: AliasSet
13
14
  private join: JoinSet
14
15
  private root: EntityCursor
@@ -19,9 +20,9 @@ export class EntityListQueryPrinter {
19
20
  constructor(
20
21
  private model: Model,
21
22
  private dialect: Dialect,
22
- private entityName: string,
23
+ public readonly entityName: string,
23
24
  private params: unknown[],
24
- private args: EntityListArguments = {},
25
+ private args: SqlArguments = {},
25
26
  fields?: FieldRequest[],
26
27
  aliases?: AliasSet
27
28
  ) {
@@ -43,12 +44,14 @@ export class EntityListQueryPrinter {
43
44
  this.populateWhere(this.root, args.where, this.where)
44
45
  }
45
46
  if (args.orderBy) {
46
- this.populateOrderBy(this.root, args.orderBy)
47
+ this.traverseOrderBy(args.orderBy, (field, cursor, order) => {
48
+ this.orderBy.push(cursor.native(field) + ' ' + order)
49
+ })
47
50
  }
48
51
  }
49
52
 
50
- private sub(entityName: string, args?: EntityListArguments, fields?: FieldRequest[]): EntityListQueryPrinter {
51
- return new EntityListQueryPrinter(this.model, this.dialect, entityName, this.params, args, fields, this.aliases)
53
+ private sub(entityName: string, args?: SqlArguments, fields?: FieldRequest[]): EntitySqlPrinter {
54
+ return new EntitySqlPrinter(this.model, this.dialect, entityName, this.params, args, fields, this.aliases)
52
55
  }
53
56
 
54
57
  private populateColumns(cursor: Cursor, fields: FieldRequest[]): void {
@@ -128,7 +131,9 @@ export class EntityListQueryPrinter {
128
131
  break
129
132
  }
130
133
  case "isNull": {
131
- let f = cursor.ref(where.field)
134
+ let f = cursor.prop(where.field).type.kind == 'lookup'
135
+ ? cursor.child(where.field).ref('id')
136
+ : cursor.ref(where.field)
132
137
  if (where.yes) {
133
138
  exps.push(`${f} IS NULL`)
134
139
  } else {
@@ -261,13 +266,17 @@ export class EntityListQueryPrinter {
261
266
  return printClause("AND", exps)
262
267
  }
263
268
 
264
- private populateOrderBy(cursor: Cursor, orderBy: OrderBy): void {
269
+ traverseOrderBy(orderBy: OrderBy, cb: (field: string, cursor: Cursor, order: SortOrder) => void) {
270
+ this.visitOrderBy(this.root, orderBy, cb)
271
+ }
272
+
273
+ private visitOrderBy(cursor: Cursor, orderBy: OrderBy, cb: (field: string, cursor: Cursor, order: SortOrder) => void) {
265
274
  for (let field in orderBy) {
266
275
  let spec = orderBy[field]
267
276
  if (typeof spec == "string") {
268
- this.orderBy.push(`${cursor.native(field)} ${spec}`)
277
+ cb(field, cursor, spec)
269
278
  } else {
270
- this.populateOrderBy(cursor.child(field), spec)
279
+ this.visitOrderBy(cursor.child(field), spec, cb)
271
280
  }
272
281
  }
273
282
  }
@@ -280,14 +289,18 @@ export class EntityListQueryPrinter {
280
289
  return escapeIdentifier(this.dialect, name)
281
290
  }
282
291
 
283
- addWhereDerivedFrom(field: string, parentIdExp: string): this {
292
+ private addWhereDerivedFrom(field: string, parentIdExp: string): this {
284
293
  this.where.push(`${this.root.native(field)} = ${parentIdExp}`)
285
294
  return this
286
295
  }
287
296
 
297
+ hasColumns(): boolean {
298
+ return this.columns.size() > 0
299
+ }
300
+
288
301
  printColumnList(options?: {withAliases?: boolean}): string {
302
+ assert(this.hasColumns())
289
303
  let names = this.columns.names()
290
- assert(names.length > 0)
291
304
  if (options?.withAliases) {
292
305
  names = names.map((name, idx) => `${name} AS _c${idx}`)
293
306
  }
@@ -322,7 +335,110 @@ export class EntityListQueryPrinter {
322
335
  return `SELECT ${this.printColumnList({withAliases: true})} ${this.printFrom()}`
323
336
  }
324
337
 
325
- printAsJsonRows(): string {
338
+ printAsCount(): string {
339
+ if (this.args.offset || this.args.limit) {
340
+ return `SELECT count(*) FROM (SELECT true ${this.printFrom()}) AS rows`
341
+ } else {
342
+ return `SELECT count(*) ${this.printFrom()}`
343
+ }
344
+ }
345
+
346
+ private printAsJsonRows(): string {
326
347
  return `SELECT ${this.printColumnListAsJsonArray()} AS row ${this.printFrom()}`
327
348
  }
328
349
  }
350
+
351
+
352
+ export class QueryableSqlPrinter {
353
+ private printers: EntitySqlPrinter[] = []
354
+ private orders: SortOrder[] = []
355
+ private orderColumns: string[][] = []
356
+
357
+ constructor(
358
+ private model: Model,
359
+ private dialect: Dialect,
360
+ private queryableName: string,
361
+ private params: unknown[],
362
+ private args: SqlArguments = {},
363
+ fields?: FieldsByEntity
364
+ ) {
365
+ for (let entityName of getQueryableEntities(this.model, this.queryableName)) {
366
+ let entityFields = fields?.[entityName]
367
+
368
+ let printer = new EntitySqlPrinter(
369
+ model,
370
+ dialect,
371
+ entityName,
372
+ this.params,
373
+ {where: args.where},
374
+ entityFields
375
+ )
376
+
377
+ if (this.args.orderBy) {
378
+ let cols: string[] = []
379
+ this.orders.length = 0
380
+ printer.traverseOrderBy(this.args.orderBy, (field, cursor, order) => {
381
+ let col = field == '_type' ? `'${entityName}'` : cursor.native(field)
382
+ this.orders.push(order)
383
+ cols.push(`${col} AS o${this.orders.length}`)
384
+ })
385
+ this.orderColumns.push(cols)
386
+ }
387
+
388
+ this.printers.push(printer)
389
+ }
390
+ }
391
+
392
+ print(): string {
393
+ let from = this.printers.map((printer, idx) => {
394
+ let cols: string[] = []
395
+ cols.push(`'${printer.entityName}' AS e`)
396
+ if (printer.hasColumns()) {
397
+ cols.push(printer.printColumnListAsJsonArray() + ' AS d')
398
+ } else {
399
+ cols.push('null AS d')
400
+ }
401
+ cols.push(...this.orderColumns[idx])
402
+ return `SELECT ${cols.join(', ')} ${printer.printFrom()}`
403
+ }).join('\nUNION ALL\n')
404
+
405
+ let args = this.printArgs()
406
+ if (args) {
407
+ return `SELECT e, d FROM (\n${from}\n) AS rows` + args
408
+ } else {
409
+ return from
410
+ }
411
+ }
412
+
413
+ printAsCount(): string {
414
+ let union = this.orders.length
415
+ ? this.printers.map((printer, idx) => {
416
+ return `SELECT ${this.orderColumns[idx].join(', ')} ${printer.printFrom()}`
417
+ })
418
+ : this.printers.map(printer => {
419
+ return `SELECT true ${printer.printFrom()}`
420
+ })
421
+
422
+ let from = union.join('\nUNION ALL\n')
423
+ let args = this.printArgs()
424
+ if (args) {
425
+ from = `SELECT true FROM (\n${from}\n) AS src` + args
426
+ }
427
+
428
+ return `SELECT count(*) FROM (\n${from}\n) AS rows`
429
+ }
430
+
431
+ private printArgs(): string {
432
+ let sql = ''
433
+ if (this.orders.length) {
434
+ sql += '\nORDER BY ' + this.orders.map((o, idx) => `o${idx + 1} ${o}`).join(', ')
435
+ }
436
+ if (this.args.offset) {
437
+ sql += `\nOFFSET ${this.args.offset}`
438
+ }
439
+ if (this.args.limit) {
440
+ sql += `\nLIMIT ${this.args.limit}`
441
+ }
442
+ return sql
443
+ }
444
+ }
package/src/sql/query.ts CHANGED
@@ -1,7 +1,7 @@
1
- import {assertNotNull} from "@subsquid/util-internal"
2
- import assert from "assert"
3
- import type {Dialect} from "../dialect"
4
- import type {EntityListArguments, Where} from "../ir/args"
1
+ import {assertNotNull} from '@subsquid/util-internal'
2
+ import assert from 'assert'
3
+ import type {Dialect} from '../dialect'
4
+ import type {SqlArguments, Where} from '../ir/args'
5
5
  import {
6
6
  decodeRelayConnectionCursor,
7
7
  encodeRelayConnectionCursor,
@@ -9,12 +9,12 @@ import {
9
9
  RelayConnectionPageInfo,
10
10
  RelayConnectionRequest,
11
11
  RelayConnectionResponse
12
- } from "../ir/connection"
13
- import type {FieldRequest} from "../ir/fields"
14
- import type {Model} from "../model"
15
- import {toSafeInteger} from "../util/util"
16
- import {mapRows} from "./mapping"
17
- import {EntityListQueryPrinter} from "./printer"
12
+ } from '../ir/connection'
13
+ import type {AnyFields, FieldRequest} from '../ir/fields'
14
+ import type {Model} from '../model'
15
+ import {toSafeInteger} from '../util/util'
16
+ import {mapQueryableRows, mapRows} from './mapping'
17
+ import {EntitySqlPrinter, QueryableSqlPrinter} from './printer'
18
18
 
19
19
 
20
20
  export interface Query<T> {
@@ -24,22 +24,32 @@ export interface Query<T> {
24
24
  }
25
25
 
26
26
 
27
- export class EntityListQuery implements Query<any[]> {
27
+ export class ListQuery implements Query<any[]> {
28
28
  public readonly sql: string
29
29
  public readonly params: unknown[] = []
30
30
 
31
31
  constructor(
32
32
  model: Model,
33
33
  dialect: Dialect,
34
- entityName: string,
35
- private fields: FieldRequest[],
36
- args: EntityListArguments
34
+ typeName: string,
35
+ private fields: AnyFields,
36
+ args: SqlArguments
37
37
  ) {
38
- this.sql = new EntityListQueryPrinter(model, dialect, entityName, this.params, args, fields).print()
38
+ if (model[typeName].kind == 'entity') {
39
+ assert(Array.isArray(fields))
40
+ this.sql = new EntitySqlPrinter(model, dialect, typeName, this.params, args, fields).print()
41
+ } else {
42
+ assert(!Array.isArray(fields))
43
+ this.sql = new QueryableSqlPrinter(model, dialect, typeName, this.params, args, fields).print()
44
+ }
39
45
  }
40
46
 
41
47
  map(rows: any[][]): any[] {
42
- return mapRows(rows, this.fields)
48
+ if (Array.isArray(this.fields)) {
49
+ return mapRows(rows, this.fields)
50
+ } else {
51
+ return mapQueryableRows(rows, this.fields)
52
+ }
43
53
  }
44
54
  }
45
55
 
@@ -55,7 +65,7 @@ export class EntityByIdQuery {
55
65
  private fields: FieldRequest[],
56
66
  id: string
57
67
  ) {
58
- this.sql = new EntityListQueryPrinter(
68
+ this.sql = new EntitySqlPrinter(
59
69
  model,
60
70
  dialect,
61
71
  entityName,
@@ -72,17 +82,18 @@ export class EntityByIdQuery {
72
82
  }
73
83
 
74
84
 
75
- export class EntityCountQuery implements Query<number> {
85
+ export class CountQuery implements Query<number> {
76
86
  public readonly sql: string
77
87
  public readonly params: unknown[] = []
78
88
 
79
89
  constructor(
80
90
  model: Model,
81
91
  dialect: Dialect,
82
- entityName: string,
92
+ typeName: string,
83
93
  where?: Where
84
94
  ) {
85
- this.sql = 'SELECT count(*) ' + new EntityListQueryPrinter(model, dialect, entityName, this.params, {where}).printFrom()
95
+ let Printer = model[typeName].kind == 'entity' ? EntitySqlPrinter : QueryableSqlPrinter
96
+ this.sql = new Printer(model, dialect, typeName, this.params, {where}).printAsCount()
86
97
  }
87
98
 
88
99
  map(rows: any[][]): number {
@@ -91,12 +102,12 @@ export class EntityCountQuery implements Query<number> {
91
102
  }
92
103
 
93
104
 
94
- export class EntityConnectionQuery implements Query<RelayConnectionResponse> {
105
+ export class ConnectionQuery implements Query<RelayConnectionResponse> {
95
106
  public readonly sql: string
96
107
  public readonly params: unknown[] = []
97
108
  private offset = 0
98
109
  private limit = 100
99
- private edgeNode?: FieldRequest[]
110
+ private edgeNode?: AnyFields
100
111
  private edgeCursor?: boolean
101
112
  private pageInfo?: boolean
102
113
  private totalCount?: boolean
@@ -104,30 +115,39 @@ export class EntityConnectionQuery implements Query<RelayConnectionResponse> {
104
115
  constructor(
105
116
  model: Model,
106
117
  dialect: Dialect,
107
- entityName: string,
108
- req: RelayConnectionRequest
118
+ typeName: string,
119
+ req: RelayConnectionRequest<AnyFields>
109
120
  ) {
110
121
  this.setOffsetAndLimit(req)
111
122
  this.edgeCursor = req.edgeCursor
112
123
  this.pageInfo = req.pageInfo
113
124
  this.totalCount = req.totalCount
114
125
 
115
- let printer = new EntityListQueryPrinter(model, dialect, entityName, this.params, {
126
+ let args = {
116
127
  orderBy: req.orderBy,
117
128
  where: req.where,
118
129
  offset: this.offset,
119
130
  limit: this.limit + 1
120
- }, req.edgeNode)
131
+ }
132
+
133
+ let printer
134
+ if (model[typeName].kind == 'entity') {
135
+ assert(req.edgeNode == null || Array.isArray(req.edgeNode))
136
+ printer = new EntitySqlPrinter(model, dialect, typeName, this.params, args, req.edgeNode)
137
+ } else {
138
+ assert(req.edgeNode == null || !Array.isArray(req.edgeNode))
139
+ printer = new QueryableSqlPrinter(model, dialect, typeName, this.params, args, req.edgeNode)
140
+ }
121
141
 
122
- if (req.edgeNode?.length) {
142
+ if (req.edgeNode) {
123
143
  this.edgeNode = req.edgeNode
124
144
  this.sql = printer.print()
125
145
  } else {
126
- this.sql = `SELECT count(*) FROM (SELECT true ${printer.printFrom()}) AS rows`
146
+ this.sql = printer.printAsCount()
127
147
  }
128
148
  }
129
149
 
130
- private setOffsetAndLimit(req: RelayConnectionRequest): void {
150
+ private setOffsetAndLimit(req: RelayConnectionRequest<unknown>): void {
131
151
  if (req.after != null) {
132
152
  this.offset = assertNotNull(decodeRelayConnectionCursor(req.after))
133
153
  }
@@ -140,7 +160,7 @@ export class EntityConnectionQuery implements Query<RelayConnectionResponse> {
140
160
  map(rows: any[][]): RelayConnectionResponse {
141
161
  let res: RelayConnectionResponse = {}
142
162
  if (this.edgeNode) {
143
- let nodes = mapRows(rows, this.edgeNode)
163
+ let nodes = Array.isArray(this.edgeNode) ? mapRows(rows, this.edgeNode) : mapQueryableRows(rows, this.edgeNode)
144
164
  let edges: RelayConnectionEdge[] = new Array(Math.min(this.limit, nodes.length))
145
165
  for (let i = 0; i < edges.length; i++) {
146
166
  edges[i] = {
@@ -0,0 +1,258 @@
1
+ import {useDatabase, useServer} from './setup'
2
+
3
+ describe('queryable interfaces', function() {
4
+ useDatabase([
5
+ `create table foo (id text primary key, name text, foo int)`,
6
+ `create table bar (id text primary key, name text, bar int)`,
7
+ `create table ref (id text primary key, name text, foo_id text not null unique references foo, bar_id text not null unique references bar)`,
8
+ `create table baz (id text primary key, name text, ref_id text references ref, baz int)`,
9
+ `insert into foo (id, name, foo) values ('foo-1', 'hello-foo-1', 1)`,
10
+ `insert into foo (id, name, foo) values ('foo-2', 'hello-foo-2', 2)`,
11
+ `insert into bar (id, name, bar) values ('bar-1', 'hello-bar-1', 10)`,
12
+ `insert into bar (id, name, bar) values ('bar-2', 'hello-bar-2', 20)`,
13
+ `insert into ref (id, name, foo_id, bar_id) values ('1', 'ref-1', 'foo-1', 'bar-2')`,
14
+ `insert into ref (id, name, foo_id, bar_id) values ('2', 'ref-2', 'foo-2', 'bar-1')`,
15
+ `insert into baz (id, name, baz, ref_id) values ('baz-1', 'hello-baz-1', 100, '1')`,
16
+ `create table one (id text primary key)`,
17
+ `create table two (id text primary key)`,
18
+ `create table relation (id text primary key, one_id text references one, two_id text references two)`,
19
+ `insert into one (id) values ('1-1')`,
20
+ `insert into one (id) values ('1-2')`,
21
+ `insert into two (id) values ('2-1')`,
22
+ `insert into two (id) values ('2-2')`,
23
+ `insert into relation (id, one_id, two_id) values ('r-1', '1-1', '2-1')`,
24
+ `insert into relation (id, one_id, two_id) values ('r-2', '1-2', '2-1')`,
25
+ ])
26
+
27
+ const client = useServer(`
28
+ interface Entity @query {
29
+ id: ID!
30
+ name: String
31
+ ref: Ref
32
+ }
33
+
34
+ type Ref @entity {
35
+ id: ID!
36
+ name: String
37
+ foo: Foo! @unique
38
+ bar: Bar! @unique
39
+ }
40
+
41
+ type Foo implements Entity @entity {
42
+ id: ID!
43
+ name: String
44
+ ref: Ref @derivedFrom(field: "foo")
45
+ foo: Int
46
+ }
47
+
48
+ type Bar implements Entity @entity {
49
+ id: ID!
50
+ name: String
51
+ ref: Ref @derivedFrom(field: "bar")
52
+ bar: Int
53
+ }
54
+
55
+ type Baz implements Entity @entity {
56
+ id: ID!
57
+ name: String
58
+ ref: Ref
59
+ baz: Int
60
+ }
61
+
62
+ interface Number @query {
63
+ id: ID!
64
+ relations: [Relation!]!
65
+ }
66
+
67
+ type One implements Number @entity {
68
+ id: ID!
69
+ relations: [Relation!]! @derivedFrom(field: "one")
70
+ }
71
+
72
+ type Two implements Number @entity {
73
+ id: ID!
74
+ relations: [Relation!]! @derivedFrom(field: "two")
75
+ }
76
+
77
+ type Relation @entity {
78
+ id: ID!
79
+ one: One
80
+ two: Two
81
+ }
82
+ `)
83
+
84
+ it('fetching', function() {
85
+ return client.test(`
86
+ query {
87
+ entities(orderBy: id_ASC) {
88
+ id
89
+ name
90
+ ref {
91
+ id
92
+ name
93
+ }
94
+ ... on Foo { foo }
95
+ ... on Bar { bar }
96
+ ... on Baz { baz }
97
+ }
98
+ }
99
+ `, {
100
+ entities: [
101
+ {
102
+ id: 'bar-1',
103
+ name: 'hello-bar-1',
104
+ ref: {
105
+ id: '2',
106
+ name: 'ref-2'
107
+ },
108
+ bar: 10
109
+ },
110
+ {
111
+ id: 'bar-2',
112
+ name: 'hello-bar-2',
113
+ ref: {
114
+ id: '1',
115
+ name: 'ref-1'
116
+ },
117
+ bar: 20
118
+ },
119
+ {
120
+ id: 'baz-1',
121
+ name: 'hello-baz-1',
122
+ ref: {
123
+ id: '1',
124
+ name: 'ref-1'
125
+ },
126
+ baz: 100
127
+ },
128
+ {
129
+ id: 'foo-1',
130
+ name: 'hello-foo-1',
131
+ ref: {
132
+ id: '1',
133
+ name: 'ref-1'
134
+ },
135
+ foo: 1
136
+ },
137
+ {
138
+ id: 'foo-2',
139
+ name: 'hello-foo-2',
140
+ ref: {
141
+ id: '2',
142
+ name: 'ref-2'
143
+ },
144
+ foo: 2
145
+ }
146
+ ]
147
+ })
148
+ })
149
+
150
+ it('sorting by entity type', function() {
151
+ return client.test(`
152
+ query {
153
+ entities(orderBy: [_type_DESC, id_ASC]) {
154
+ id
155
+ }
156
+ }
157
+ `, {
158
+ entities: [
159
+ {id: 'foo-1'},
160
+ {id: 'foo-2'},
161
+ {id: 'baz-1'},
162
+ {id: 'bar-1'},
163
+ {id: 'bar-2'}
164
+ ]
165
+ })
166
+ })
167
+
168
+ it('pagination', function() {
169
+ return client.test(`
170
+ query {
171
+ page1: entitiesConnection(orderBy: id_ASC, first: 2) {
172
+ ...fields
173
+ }
174
+ page2: entitiesConnection(orderBy: id_ASC, first: 2, after: "2") {
175
+ ...fields
176
+ }
177
+ page3: entitiesConnection(orderBy: id_ASC, first: 2, after: "4") {
178
+ ...fields
179
+ }
180
+ }
181
+
182
+ fragment fields on EntitiesConnection {
183
+ edges {
184
+ cursor
185
+ node {
186
+ ... on Foo { foo }
187
+ ... on Bar { bar }
188
+ }
189
+ }
190
+ pageInfo {
191
+ hasNextPage
192
+ hasPreviousPage
193
+ startCursor
194
+ endCursor
195
+ }
196
+ totalCount
197
+ }
198
+ `, {
199
+ page1: {
200
+ edges: [
201
+ {cursor: '1', node: {bar: 10}},
202
+ {cursor: '2', node: {bar: 20}},
203
+ ],
204
+ pageInfo: {
205
+ hasNextPage: true,
206
+ hasPreviousPage: false,
207
+ startCursor: '1',
208
+ endCursor: '2'
209
+ },
210
+ totalCount: 5
211
+ },
212
+ page2: {
213
+ edges: [
214
+ {cursor: '3', node: {}},
215
+ {cursor: '4', node: {foo: 1}},
216
+ ],
217
+ pageInfo: {
218
+ hasNextPage: true,
219
+ hasPreviousPage: true,
220
+ startCursor: '3',
221
+ endCursor: '4'
222
+ },
223
+ totalCount: 5
224
+ },
225
+ page3: {
226
+ edges: [
227
+ {cursor: '5', node: {foo: 2}},
228
+ ],
229
+ pageInfo: {
230
+ hasNextPage: false,
231
+ hasPreviousPage: true,
232
+ startCursor: '5',
233
+ endCursor: '5'
234
+ },
235
+ totalCount: 5
236
+ }
237
+ })
238
+ })
239
+
240
+ it('list lookup fields in interfaces', function() {
241
+ return client.test(`
242
+ query {
243
+ numbers(orderBy: id_ASC) {
244
+ id
245
+ relations { id }
246
+ __typename
247
+ }
248
+ }
249
+ `, {
250
+ numbers: [
251
+ {__typename: 'One', id: '1-1', relations: [{id: 'r-1'}]},
252
+ {__typename: 'One', id: '1-2', relations: [{id: 'r-2'}]},
253
+ {__typename: 'Two', id: '2-1', relations: [{id: 'r-1'}, {id: 'r-2'}]},
254
+ {__typename: 'Two', id: '2-2', relations: []}
255
+ ]
256
+ })
257
+ })
258
+ })