@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.
- package/lib/context.d.ts +5 -2
- package/lib/context.d.ts.map +1 -1
- package/lib/ir/args.d.ts +1 -1
- package/lib/ir/args.d.ts.map +1 -1
- package/lib/ir/connection.d.ts +3 -4
- package/lib/ir/connection.d.ts.map +1 -1
- package/lib/ir/connection.js.map +1 -1
- package/lib/ir/fields.d.ts +6 -2
- package/lib/ir/fields.d.ts.map +1 -1
- package/lib/ir/fields.js +15 -0
- package/lib/ir/fields.js.map +1 -1
- package/lib/limit.size.d.ts +7 -2
- package/lib/limit.size.d.ts.map +1 -1
- package/lib/limit.size.js +106 -12
- package/lib/limit.size.js.map +1 -1
- package/lib/main.js +6 -9
- package/lib/main.js.map +1 -1
- package/lib/model.d.ts +3 -1
- package/lib/model.d.ts.map +1 -1
- package/lib/model.schema.d.ts +2 -2
- package/lib/model.schema.d.ts.map +1 -1
- package/lib/model.schema.js +29 -7
- package/lib/model.schema.js.map +1 -1
- package/lib/model.tools.d.ts +6 -1
- package/lib/model.tools.d.ts.map +1 -1
- package/lib/model.tools.js +111 -8
- package/lib/model.tools.js.map +1 -1
- package/lib/opencrud/orderBy.d.ts +2 -2
- package/lib/opencrud/orderBy.d.ts.map +1 -1
- package/lib/opencrud/orderBy.js +13 -17
- package/lib/opencrud/orderBy.js.map +1 -1
- package/lib/opencrud/schema.d.ts +4 -4
- package/lib/opencrud/schema.d.ts.map +1 -1
- package/lib/opencrud/schema.js +60 -64
- package/lib/opencrud/schema.js.map +1 -1
- package/lib/opencrud/tree.d.ts +9 -7
- package/lib/opencrud/tree.d.ts.map +1 -1
- package/lib/opencrud/tree.js +32 -14
- package/lib/opencrud/tree.js.map +1 -1
- package/lib/server.d.ts +16 -12
- package/lib/server.d.ts.map +1 -1
- package/lib/server.js +29 -4
- package/lib/server.js.map +1 -1
- package/lib/sql/cursor.js +2 -2
- package/lib/sql/cursor.js.map +1 -1
- package/lib/sql/mapping.d.ts +3 -1
- package/lib/sql/mapping.d.ts.map +1 -1
- package/lib/sql/mapping.js +16 -1
- package/lib/sql/mapping.js.map +1 -1
- package/lib/sql/printer.d.ts +29 -11
- package/lib/sql/printer.d.ts.map +1 -1
- package/lib/sql/printer.js +106 -10
- package/lib/sql/printer.js.map +1 -1
- package/lib/sql/query.d.ts +11 -11
- package/lib/sql/query.d.ts.map +1 -1
- package/lib/sql/query.js +41 -19
- package/lib/sql/query.js.map +1 -1
- package/lib/test/limits.test.d.ts +2 -0
- package/lib/test/limits.test.d.ts.map +1 -0
- package/lib/test/limits.test.js +159 -0
- package/lib/test/limits.test.js.map +1 -0
- package/lib/test/queryable.test.d.ts +2 -0
- package/lib/test/queryable.test.d.ts.map +1 -0
- package/lib/test/queryable.test.js +255 -0
- package/lib/test/queryable.test.js.map +1 -0
- package/lib/test/setup.d.ts +2 -1
- package/lib/test/setup.d.ts.map +1 -1
- package/lib/test/setup.js +5 -2
- package/lib/test/setup.js.map +1 -1
- package/lib/util/execute.d.ts +5 -0
- package/lib/util/execute.d.ts.map +1 -0
- package/lib/util/execute.js +28 -0
- package/lib/util/execute.js.map +1 -0
- package/lib/util/limit.d.ts +11 -0
- package/lib/util/limit.d.ts.map +1 -0
- package/lib/util/limit.js +39 -0
- package/lib/util/limit.js.map +1 -0
- package/package.json +3 -3
- package/src/context.ts +5 -2
- package/src/ir/args.ts +1 -1
- package/src/ir/connection.ts +3 -4
- package/src/ir/fields.ts +22 -2
- package/src/limit.size.ts +122 -13
- package/src/main.ts +18 -20
- package/src/model.schema.ts +40 -13
- package/src/model.tools.ts +121 -8
- package/src/model.ts +3 -1
- package/src/opencrud/orderBy.ts +13 -17
- package/src/opencrud/schema.ts +86 -85
- package/src/opencrud/tree.ts +55 -26
- package/src/server.ts +66 -26
- package/src/sql/cursor.ts +2 -2
- package/src/sql/mapping.ts +18 -1
- package/src/sql/printer.ts +137 -21
- package/src/sql/query.ts +50 -30
- package/src/test/limits.test.ts +163 -0
- package/src/test/queryable.test.ts +258 -0
- package/src/test/setup.ts +6 -3
- package/src/util/execute.ts +53 -0
- package/src/util/limit.ts +34 -0
package/src/sql/query.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {assertNotNull} from
|
|
2
|
-
import assert from
|
|
3
|
-
import type {Dialect} from
|
|
4
|
-
import type {
|
|
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
|
|
13
|
-
import type {FieldRequest} from
|
|
14
|
-
import type {Model} from
|
|
15
|
-
import {toSafeInteger} from
|
|
16
|
-
import {mapRows} from
|
|
17
|
-
import {
|
|
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
|
|
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
|
-
|
|
35
|
-
private fields:
|
|
36
|
-
args:
|
|
34
|
+
typeName: string,
|
|
35
|
+
private fields: AnyFields,
|
|
36
|
+
args: SqlArguments
|
|
37
37
|
) {
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
92
|
+
typeName: string,
|
|
83
93
|
where?: Where
|
|
84
94
|
) {
|
|
85
|
-
|
|
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
|
|
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?:
|
|
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
|
-
|
|
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
|
|
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
|
-
}
|
|
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
|
|
142
|
+
if (req.edgeNode) {
|
|
123
143
|
this.edgeNode = req.edgeNode
|
|
124
144
|
this.sql = printer.print()
|
|
125
145
|
} else {
|
|
126
|
-
this.sql =
|
|
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
|
|
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
|
+
}
|