@subsquid/openreader 2.0.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 (100) hide show
  1. package/lib/context.d.ts +5 -2
  2. package/lib/context.d.ts.map +1 -1
  3. package/lib/ir/args.d.ts +1 -1
  4. package/lib/ir/args.d.ts.map +1 -1
  5. package/lib/ir/connection.d.ts +3 -4
  6. package/lib/ir/connection.d.ts.map +1 -1
  7. package/lib/ir/connection.js.map +1 -1
  8. package/lib/ir/fields.d.ts +6 -2
  9. package/lib/ir/fields.d.ts.map +1 -1
  10. package/lib/ir/fields.js +15 -0
  11. package/lib/ir/fields.js.map +1 -1
  12. package/lib/limit.size.d.ts +7 -2
  13. package/lib/limit.size.d.ts.map +1 -1
  14. package/lib/limit.size.js +106 -12
  15. package/lib/limit.size.js.map +1 -1
  16. package/lib/main.js +6 -9
  17. package/lib/main.js.map +1 -1
  18. package/lib/model.d.ts +3 -1
  19. package/lib/model.d.ts.map +1 -1
  20. package/lib/model.schema.d.ts +2 -2
  21. package/lib/model.schema.d.ts.map +1 -1
  22. package/lib/model.schema.js +29 -7
  23. package/lib/model.schema.js.map +1 -1
  24. package/lib/model.tools.d.ts +6 -1
  25. package/lib/model.tools.d.ts.map +1 -1
  26. package/lib/model.tools.js +111 -8
  27. package/lib/model.tools.js.map +1 -1
  28. package/lib/opencrud/orderBy.d.ts +2 -2
  29. package/lib/opencrud/orderBy.d.ts.map +1 -1
  30. package/lib/opencrud/orderBy.js +13 -17
  31. package/lib/opencrud/orderBy.js.map +1 -1
  32. package/lib/opencrud/schema.d.ts +4 -4
  33. package/lib/opencrud/schema.d.ts.map +1 -1
  34. package/lib/opencrud/schema.js +60 -64
  35. package/lib/opencrud/schema.js.map +1 -1
  36. package/lib/opencrud/tree.d.ts +9 -7
  37. package/lib/opencrud/tree.d.ts.map +1 -1
  38. package/lib/opencrud/tree.js +32 -14
  39. package/lib/opencrud/tree.js.map +1 -1
  40. package/lib/server.d.ts +16 -12
  41. package/lib/server.d.ts.map +1 -1
  42. package/lib/server.js +29 -4
  43. package/lib/server.js.map +1 -1
  44. package/lib/sql/cursor.js +2 -2
  45. package/lib/sql/cursor.js.map +1 -1
  46. package/lib/sql/mapping.d.ts +3 -1
  47. package/lib/sql/mapping.d.ts.map +1 -1
  48. package/lib/sql/mapping.js +16 -1
  49. package/lib/sql/mapping.js.map +1 -1
  50. package/lib/sql/printer.d.ts +29 -11
  51. package/lib/sql/printer.d.ts.map +1 -1
  52. package/lib/sql/printer.js +106 -10
  53. package/lib/sql/printer.js.map +1 -1
  54. package/lib/sql/query.d.ts +11 -11
  55. package/lib/sql/query.d.ts.map +1 -1
  56. package/lib/sql/query.js +41 -19
  57. package/lib/sql/query.js.map +1 -1
  58. package/lib/test/limits.test.d.ts +2 -0
  59. package/lib/test/limits.test.d.ts.map +1 -0
  60. package/lib/test/limits.test.js +159 -0
  61. package/lib/test/limits.test.js.map +1 -0
  62. package/lib/test/queryable.test.d.ts +2 -0
  63. package/lib/test/queryable.test.d.ts.map +1 -0
  64. package/lib/test/queryable.test.js +255 -0
  65. package/lib/test/queryable.test.js.map +1 -0
  66. package/lib/test/setup.d.ts +2 -1
  67. package/lib/test/setup.d.ts.map +1 -1
  68. package/lib/test/setup.js +5 -2
  69. package/lib/test/setup.js.map +1 -1
  70. package/lib/util/execute.d.ts +5 -0
  71. package/lib/util/execute.d.ts.map +1 -0
  72. package/lib/util/execute.js +28 -0
  73. package/lib/util/execute.js.map +1 -0
  74. package/lib/util/limit.d.ts +11 -0
  75. package/lib/util/limit.d.ts.map +1 -0
  76. package/lib/util/limit.js +39 -0
  77. package/lib/util/limit.js.map +1 -0
  78. package/package.json +3 -3
  79. package/src/context.ts +5 -2
  80. package/src/ir/args.ts +1 -1
  81. package/src/ir/connection.ts +3 -4
  82. package/src/ir/fields.ts +22 -2
  83. package/src/limit.size.ts +122 -13
  84. package/src/main.ts +18 -20
  85. package/src/model.schema.ts +40 -13
  86. package/src/model.tools.ts +121 -8
  87. package/src/model.ts +3 -1
  88. package/src/opencrud/orderBy.ts +13 -17
  89. package/src/opencrud/schema.ts +86 -85
  90. package/src/opencrud/tree.ts +55 -26
  91. package/src/server.ts +66 -26
  92. package/src/sql/cursor.ts +2 -2
  93. package/src/sql/mapping.ts +18 -1
  94. package/src/sql/printer.ts +137 -21
  95. package/src/sql/query.ts +50 -30
  96. package/src/test/limits.test.ts +163 -0
  97. package/src/test/queryable.test.ts +258 -0
  98. package/src/test/setup.ts +6 -3
  99. package/src/util/execute.ts +53 -0
  100. package/src/util/limit.ts +34 -0
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,163 @@
1
+ import expect from 'expect'
2
+ import {useDatabase, useServer} from './setup'
3
+
4
+
5
+ describe('response size limits', function() {
6
+ useDatabase([
7
+ `create table "order1" (id text primary key)`,
8
+ `create table item1 (id text primary key, order_id text, name text)`,
9
+ `create table "order2" (id text primary key)`,
10
+ `create table item2 (id text primary key, order_id text, name text)`,
11
+ `create table "order3" (id text primary key)`,
12
+ `create table item3 (id text primary key, order_id text, name text)`,
13
+ ])
14
+
15
+ const client = useServer(`
16
+ type Order1 @entity {
17
+ id: ID!
18
+ items: [Item1!]! @derivedFrom(field: "order")
19
+ }
20
+
21
+ type Item1 @entity {
22
+ id: ID!
23
+ order: Order1!
24
+ name: String
25
+ }
26
+
27
+ type Order2 @entity @cardinality(value: 10) {
28
+ id: ID!
29
+ items: [Item2!]! @derivedFrom(field: "order")
30
+ }
31
+
32
+ type Item2 @entity {
33
+ id: ID!
34
+ order: Order2!
35
+ name: String
36
+ }
37
+
38
+ type Order3 @entity {
39
+ id: ID!
40
+ items: [Item3!]! @derivedFrom(field: "order") @cardinality(value: 10)
41
+ }
42
+
43
+ type Item3 @entity {
44
+ id: ID!
45
+ order: Order3!
46
+ name: String @byteWeight(value: 10.0)
47
+ }
48
+ `, {
49
+ maxResponseNodes: 50,
50
+ maxRootFields: 3
51
+ })
52
+
53
+ it('unlimited requests fail', async function() {
54
+ let result = await client.query(`
55
+ query {
56
+ order1s {
57
+ id
58
+ }
59
+ }
60
+ `)
61
+ expect(result).toMatchObject({
62
+ data: null,
63
+ errors: [
64
+ expect.objectContaining({message: 'response might exceed the size limit', path: ['order1s']})
65
+ ]
66
+ })
67
+ })
68
+
69
+ it('limited requests work', function() {
70
+ return client.test(`
71
+ query {
72
+ order1s(limit: 10) {
73
+ items(limit: 2) {
74
+ id
75
+ }
76
+ }
77
+ }
78
+ `, {
79
+ order1s: []
80
+ })
81
+ })
82
+
83
+ it('entity level cardinalities are respected', function() {
84
+ return client.test(`
85
+ query {
86
+ order2s {
87
+ id
88
+ }
89
+ }
90
+ `, {
91
+ order2s: []
92
+ })
93
+ })
94
+
95
+ it('item cardinalities are respected', function() {
96
+ return client.test(`
97
+ query {
98
+ order3s(limit: 1) {
99
+ items { id }
100
+ }
101
+ }
102
+ `, {
103
+ order3s: []
104
+ })
105
+ })
106
+
107
+ it('@byteWeight annotations are respected', async function() {
108
+ let result = await client.query(`
109
+ query {
110
+ order3s(limit: 1) {
111
+ items(limit: 8) { name }
112
+ }
113
+ }
114
+ `)
115
+ expect(result).toEqual({
116
+ data: null,
117
+ errors: [
118
+ expect.objectContaining({
119
+ message: 'response might exceed the size limit',
120
+ path: ['order3s']
121
+ })
122
+ ]
123
+ })
124
+ await client.test(`
125
+ query {
126
+ order3s(limit: 1) {
127
+ items(limit: 4) { name }
128
+ }
129
+ }
130
+ `, {
131
+ order3s: []
132
+ })
133
+ })
134
+
135
+ it('id_in conditions are understood', function() {
136
+ return client.test(`
137
+ query {
138
+ order1s(where: {id_in: ["1", "2", "3"]}) {
139
+ id
140
+ }
141
+ }
142
+ `, {
143
+ order1s: []
144
+ })
145
+ })
146
+
147
+ it('root query fields limit', async function() {
148
+ return client.errorTest(`
149
+ query {
150
+ a: order1ById(id: "1") { id }
151
+ b: order1ById(id: "1") { id }
152
+ c: order1ById(id: "1") { id }
153
+ d: order1ById(id: "1") { id }
154
+ }
155
+ `, {
156
+ errors: [
157
+ expect.objectContaining({
158
+ message: 'only 3 root query fields allowed, but got 4'
159
+ })
160
+ ]
161
+ })
162
+ })
163
+ })
@@ -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
+ })
package/src/test/setup.ts CHANGED
@@ -4,7 +4,7 @@ import {Client} from "gql-test-client"
4
4
  import {parse} from "graphql"
5
5
  import {Client as PgClient, ClientBase, Pool} from "pg"
6
6
  import {buildModel, buildSchema} from "../model.schema"
7
- import {serve} from "../server"
7
+ import {serve, ServerOptions} from '../server'
8
8
 
9
9
 
10
10
  export function isCockroach(): boolean {
@@ -59,7 +59,7 @@ export function useDatabase(sql: string[]): void {
59
59
  }
60
60
 
61
61
 
62
- export function useServer(schema: string): Client {
62
+ export function useServer(schema: string, options?: Partial<ServerOptions>): Client {
63
63
  let client = new Client('not defined')
64
64
  let db = new Pool(db_config)
65
65
  let info: ListeningServer | undefined
@@ -69,7 +69,10 @@ export function useServer(schema: string): Client {
69
69
  model: buildModel(buildSchema(parse(schema))),
70
70
  port: 0,
71
71
  dialect: isCockroach() ? 'cockroach' : 'postgres',
72
- subscriptions: true
72
+ subscriptions: true,
73
+ subscriptionPollInterval: 500,
74
+ maxRootFields: 10,
75
+ ...options
73
76
  })
74
77
  client.endpoint = `http://localhost:${info.port}/graphql`
75
78
  })
@@ -0,0 +1,53 @@
1
+ import {getOperationRootType, GraphQLError} from 'graphql'
2
+ import {ExecutionResult} from 'graphql-ws'
3
+ import {
4
+ assertValidExecutionArguments,
5
+ buildExecutionContext,
6
+ collectFields,
7
+ execute as graphqlExecute,
8
+ ExecutionArgs,
9
+ ExecutionContext
10
+ } from 'graphql/execution/execute'
11
+ import {PromiseOrValue} from 'graphql/jsutils/PromiseOrValue'
12
+
13
+
14
+ export function executeWithLimit(maxQueries: number, args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
15
+ assertValidExecutionArguments(args.schema, args.document, args.variableValues)
16
+
17
+ let xtx = buildExecutionContext(
18
+ args.schema,
19
+ args.document,
20
+ args.rootValue,
21
+ args.contextValue,
22
+ args.variableValues,
23
+ args.operationName,
24
+ args.fieldResolver,
25
+ args.typeResolver
26
+ )
27
+
28
+ if (Array.isArray(xtx)) {
29
+ return {errors: xtx}
30
+ }
31
+
32
+ let etx = xtx as ExecutionContext
33
+ if (etx.operation.operation == 'query') {
34
+ let query = getOperationRootType(etx.schema, etx.operation)
35
+ let fields = collectFields(
36
+ etx,
37
+ query,
38
+ etx.operation.selectionSet,
39
+ Object.create(null),
40
+ Object.create(null)
41
+ )
42
+ let fieldsCount = Object.keys(fields).length
43
+ if (fieldsCount > maxQueries) {
44
+ return {
45
+ errors: [
46
+ new GraphQLError(`only ${maxQueries} root query fields allowed, but got ${fieldsCount}`)
47
+ ]
48
+ }
49
+ }
50
+ }
51
+
52
+ return graphqlExecute(args)
53
+ }