@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/model.tools.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import {unexpectedCase} from '@subsquid/util-internal'
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import {Entity, FTS_Query, Interface, JsonObject, Model, Prop, PropType} from './model'
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
const UNION_MAPS = new WeakMap<Model, Record<string, JsonObject>>()
|
|
@@ -20,8 +21,7 @@ export function buildUnionProps(model: Model, unionName: string): JsonObject {
|
|
|
20
21
|
let union = model[unionName]
|
|
21
22
|
assert(union.kind == 'union')
|
|
22
23
|
let properties: Record<string, Prop> = {}
|
|
23
|
-
for (let
|
|
24
|
-
let objectName = union.variants[i]
|
|
24
|
+
for (let objectName of union.variants) {
|
|
25
25
|
let object = model[objectName]
|
|
26
26
|
assert(object.kind == 'object')
|
|
27
27
|
Object.assign(properties, object.properties)
|
|
@@ -34,12 +34,106 @@ export function buildUnionProps(model: Model, unionName: string): JsonObject {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
const QUERYABLE_ENTITIES = new WeakMap<Model, Record<string, string[]>>()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
export function getQueryableEntities(model: Model, queryableName: string): string[] {
|
|
41
|
+
let cache = QUERYABLE_ENTITIES.get(model)
|
|
42
|
+
if (cache == null) {
|
|
43
|
+
cache = {}
|
|
44
|
+
QUERYABLE_ENTITIES.set(model, cache)
|
|
45
|
+
}
|
|
46
|
+
let entities = cache[queryableName]
|
|
47
|
+
if (entities) return entities
|
|
48
|
+
entities = cache[queryableName] = []
|
|
49
|
+
for (let name in model) {
|
|
50
|
+
let item = model[name]
|
|
51
|
+
if (item.kind == 'entity' && item.interfaces?.includes(queryableName)) {
|
|
52
|
+
entities.push(name)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return entities
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
export function getQueryableProperties(model: Model, queryableName: string): Record<string, Prop> {
|
|
60
|
+
let queryable = getInterface(model, queryableName)
|
|
61
|
+
let props: Record<string, Prop> = {}
|
|
62
|
+
for (let entityName of getQueryableEntities(model, queryableName)) {
|
|
63
|
+
let entity = getEntity(model, entityName)
|
|
64
|
+
for (let key in queryable.properties) {
|
|
65
|
+
let ep = entity.properties[key]
|
|
66
|
+
if (ep == null) throw new Error(
|
|
67
|
+
`Entity ${entityName} doesn't implement ${queryableName} properly: .${key} property is missing`
|
|
68
|
+
)
|
|
69
|
+
let prop = matchWithQueryableProp(queryable.properties[key], ep)
|
|
70
|
+
if (prop == null) throw new Error(
|
|
71
|
+
`Entity ${entityName} doesn't implement ${queryableName} properly: .${key} property types are incompatible`
|
|
72
|
+
)
|
|
73
|
+
props[key] = prop
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return props
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
function matchWithQueryableProp(queryableProp: Prop, entityProp: Prop): Prop | undefined {
|
|
81
|
+
if (!queryableProp.nullable && entityProp.nullable) return undefined
|
|
82
|
+
let qt = queryableProp.type
|
|
83
|
+
let et = entityProp.type
|
|
84
|
+
switch(et.kind) {
|
|
85
|
+
case 'fk':
|
|
86
|
+
case 'lookup':
|
|
87
|
+
if (qt.kind == 'object' && qt.name == et.entity) {
|
|
88
|
+
return {
|
|
89
|
+
type: et,
|
|
90
|
+
nullable: queryableProp.nullable,
|
|
91
|
+
description: queryableProp.description
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return undefined
|
|
95
|
+
case 'list-lookup':
|
|
96
|
+
if (qt.kind == 'list' && qt.item.type.kind == 'object' && qt.item.type.name == et.entity) {
|
|
97
|
+
return {
|
|
98
|
+
type: et,
|
|
99
|
+
nullable: false,
|
|
100
|
+
description: queryableProp.description
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined
|
|
104
|
+
default:
|
|
105
|
+
if (propTypeEquals(et, qt)) {
|
|
106
|
+
return queryableProp
|
|
107
|
+
}
|
|
108
|
+
return undefined
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
export function getUniversalProperties(model: Model, typeName: string): Record<string, Prop> {
|
|
114
|
+
let item = model[typeName]
|
|
115
|
+
switch(item.kind) {
|
|
116
|
+
case 'entity':
|
|
117
|
+
case 'object':
|
|
118
|
+
return item.properties
|
|
119
|
+
case 'union':
|
|
120
|
+
return getUnionProps(model, typeName).properties
|
|
121
|
+
case 'interface':
|
|
122
|
+
assert(item.queryable)
|
|
123
|
+
return getQueryableProperties(model, typeName)
|
|
124
|
+
default:
|
|
125
|
+
throw unexpectedCase(item.kind)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
37
130
|
export function validateModel(model: Model) {
|
|
38
131
|
// TODO: check all invariants we assume
|
|
39
132
|
validateNames(model)
|
|
40
133
|
validateUnionTypes(model)
|
|
41
134
|
validateLookups(model)
|
|
42
135
|
validateIndexes(model)
|
|
136
|
+
validateQueryableInterfaces(model)
|
|
43
137
|
}
|
|
44
138
|
|
|
45
139
|
|
|
@@ -119,7 +213,7 @@ export function validateLookups(model: Model): void {
|
|
|
119
213
|
if (prop.type.kind == 'lookup' || prop.type.kind == 'list-lookup') {
|
|
120
214
|
let lookupEntity = getEntity(model, prop.type.entity)
|
|
121
215
|
let lookupProperty = lookupEntity.properties[prop.type.field]
|
|
122
|
-
if (lookupProperty?.type.kind != 'fk' || lookupProperty.type.
|
|
216
|
+
if (lookupProperty?.type.kind != 'fk' || lookupProperty.type.entity != name) {
|
|
123
217
|
throw invalidProperty(name, key, `${prop.type.entity}.${prop.type.field} is not a foreign key pointing to ${name}`)
|
|
124
218
|
}
|
|
125
219
|
if (prop.type.kind == 'lookup' && !lookupProperty.unique) {
|
|
@@ -142,7 +236,7 @@ export function validateIndexes(model: Model): void {
|
|
|
142
236
|
index.fields.forEach(f => {
|
|
143
237
|
let prop = item.properties[f.name]
|
|
144
238
|
if (prop == null) throw new Error(
|
|
145
|
-
`Entity ${name} doesn't have a property ${f.name}, but is a part of index`
|
|
239
|
+
`Entity ${name} doesn't have a property ${f.name}, but it is a part of index`
|
|
146
240
|
)
|
|
147
241
|
switch(prop.type.kind) {
|
|
148
242
|
case "scalar":
|
|
@@ -160,6 +254,16 @@ export function validateIndexes(model: Model): void {
|
|
|
160
254
|
}
|
|
161
255
|
|
|
162
256
|
|
|
257
|
+
export function validateQueryableInterfaces(model: Model): void {
|
|
258
|
+
for (let name in model) {
|
|
259
|
+
let item = model[name]
|
|
260
|
+
if (item.kind == 'interface' && item.queryable) {
|
|
261
|
+
getQueryableProperties(model, name)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
163
267
|
function invalidProperty(item: string, key: string, msg: string): Error {
|
|
164
268
|
return new Error(`Invalid property ${item}.${key}: ${msg}`)
|
|
165
269
|
}
|
|
@@ -167,10 +271,12 @@ function invalidProperty(item: string, key: string, msg: string): Error {
|
|
|
167
271
|
|
|
168
272
|
export function propTypeEquals(a: PropType, b: PropType): boolean {
|
|
169
273
|
if (a.kind != b.kind) return false
|
|
170
|
-
if (a.kind == 'list') return propTypeEquals(a.item.type, (b as typeof a).item.type)
|
|
171
274
|
switch(a.kind) {
|
|
275
|
+
case 'list':
|
|
276
|
+
return a.item.nullable == (b as typeof a).item.nullable
|
|
277
|
+
&& propTypeEquals(a.item.type, (b as typeof a).item.type)
|
|
172
278
|
case 'fk':
|
|
173
|
-
return a.
|
|
279
|
+
return a.entity == (b as typeof a).entity
|
|
174
280
|
case 'lookup':
|
|
175
281
|
case 'list-lookup':
|
|
176
282
|
return a.entity == (b as typeof a).entity && a.field == (b as typeof a).field
|
|
@@ -194,6 +300,13 @@ export function getObject(model: Model, name: string): JsonObject {
|
|
|
194
300
|
}
|
|
195
301
|
|
|
196
302
|
|
|
303
|
+
export function getInterface(model: Model, name: string): Interface {
|
|
304
|
+
let i = model[name]
|
|
305
|
+
assert(i.kind == 'interface', `${name} expected to be an interface`)
|
|
306
|
+
return i
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
197
310
|
export function getFtsQuery(model: Model, name: string): FTS_Query {
|
|
198
311
|
let query = model[name]
|
|
199
312
|
assert(query.kind == 'fts', `${name} expected to be FTS query`)
|
package/src/model.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface Entity extends TypeMeta {
|
|
|
9
9
|
properties: Record<Name, Prop>
|
|
10
10
|
interfaces?: Name[]
|
|
11
11
|
indexes?: Index[]
|
|
12
|
+
cardinality?: number
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
|
|
@@ -22,6 +23,7 @@ export interface JsonObject extends TypeMeta {
|
|
|
22
23
|
export interface Interface extends TypeMeta {
|
|
23
24
|
kind: 'interface'
|
|
24
25
|
properties: Record<Name, Prop>
|
|
26
|
+
queryable?: boolean
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
|
|
@@ -107,7 +109,7 @@ export interface ListPropType {
|
|
|
107
109
|
|
|
108
110
|
export interface FkPropType {
|
|
109
111
|
kind: 'fk'
|
|
110
|
-
|
|
112
|
+
entity: Name
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
|
package/src/opencrud/orderBy.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "assert"
|
|
2
2
|
import type {Model} from "../model"
|
|
3
|
-
import {getUnionProps} from
|
|
3
|
+
import {getUnionProps, getUniversalProperties} from '../model.tools'
|
|
4
4
|
import {OrderBy} from "../ir/args"
|
|
5
5
|
|
|
6
6
|
|
|
@@ -19,27 +19,23 @@ export type OpenCrud_OrderBy_Mapping = ReadonlyMap<OpenCrudOrderByValue, OrderBy
|
|
|
19
19
|
const MAPPING_CACHE = new WeakMap<Model, Record<string, OpenCrud_OrderBy_Mapping>>()
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
export function getOrderByMapping(model: Model,
|
|
22
|
+
export function getOrderByMapping(model: Model, typeName: string): OpenCrud_OrderBy_Mapping {
|
|
23
23
|
let cache = MAPPING_CACHE.get(model)
|
|
24
24
|
if (cache == null) {
|
|
25
25
|
cache = {}
|
|
26
26
|
MAPPING_CACHE.set(model, cache)
|
|
27
27
|
}
|
|
28
|
-
if (cache[
|
|
29
|
-
return cache[
|
|
28
|
+
if (cache[typeName]) return cache[typeName]
|
|
29
|
+
return cache[typeName] = buildOrderByMapping(model, typeName, 2)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
function buildOrderByMapping(model: Model, typeName: string, depth: number): OpenCrud_OrderBy_Mapping {
|
|
34
34
|
if (depth <= 0) return new Map()
|
|
35
|
-
let
|
|
36
|
-
if (object.kind == 'union') {
|
|
37
|
-
object = getUnionProps(model, typeName)
|
|
38
|
-
}
|
|
39
|
-
assert(object.kind == 'entity' || object.kind == 'object')
|
|
35
|
+
let properties = getUniversalProperties(model, typeName)
|
|
40
36
|
let m = new Map<string, OrderBy>()
|
|
41
|
-
for (let key in
|
|
42
|
-
let propType =
|
|
37
|
+
for (let key in properties) {
|
|
38
|
+
let propType = properties[key].type
|
|
43
39
|
switch(propType.kind) {
|
|
44
40
|
case 'scalar':
|
|
45
41
|
case 'enum':
|
|
@@ -55,10 +51,6 @@ function buildOrderByMapping(model: Model, typeName: string, depth: number): Ope
|
|
|
55
51
|
}
|
|
56
52
|
break
|
|
57
53
|
case 'fk':
|
|
58
|
-
for (let [name, spec] of buildOrderByMapping(model, propType.foreignEntity, depth - 1)) {
|
|
59
|
-
m.set(key + '_' + name, {[key]: spec})
|
|
60
|
-
}
|
|
61
|
-
break
|
|
62
54
|
case 'lookup':
|
|
63
55
|
for (let [name, spec] of buildOrderByMapping(model, propType.entity, depth - 1)) {
|
|
64
56
|
m.set(key + '_' + name, {[key]: spec})
|
|
@@ -66,12 +58,16 @@ function buildOrderByMapping(model: Model, typeName: string, depth: number): Ope
|
|
|
66
58
|
break
|
|
67
59
|
}
|
|
68
60
|
}
|
|
61
|
+
if (model[typeName].kind == 'interface') {
|
|
62
|
+
m.set('_type_ASC', {_type: 'ASC'})
|
|
63
|
+
m.set('_type_DESC', {_type: 'DESC'})
|
|
64
|
+
}
|
|
69
65
|
return m
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
|
|
73
|
-
export function parseOrderBy(model: Model,
|
|
74
|
-
let mapping = getOrderByMapping(model,
|
|
69
|
+
export function parseOrderBy(model: Model, typeName: string, input: OpenCrudOrderByValue[]): OrderBy {
|
|
70
|
+
let mapping = getOrderByMapping(model, typeName)
|
|
75
71
|
return mergeOrderBy(
|
|
76
72
|
input.map(value => {
|
|
77
73
|
let spec = mapping.get(value)
|
package/src/opencrud/schema.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {def, unexpectedCase} from
|
|
2
|
-
import {toCamelCase, toPlural} from
|
|
3
|
-
import {UserInputError} from
|
|
4
|
-
import assert from
|
|
1
|
+
import {def, unexpectedCase} from '@subsquid/util-internal'
|
|
2
|
+
import {toCamelCase, toPlural} from '@subsquid/util-naming'
|
|
3
|
+
import {UserInputError} from 'apollo-server-core'
|
|
4
|
+
import assert from 'assert'
|
|
5
5
|
import {
|
|
6
6
|
GraphQLBoolean,
|
|
7
7
|
GraphQLEnumType,
|
|
@@ -20,25 +20,28 @@ import {
|
|
|
20
20
|
GraphQLSchema,
|
|
21
21
|
GraphQLString,
|
|
22
22
|
GraphQLUnionType
|
|
23
|
-
} from
|
|
23
|
+
} from 'graphql'
|
|
24
24
|
import {
|
|
25
25
|
GraphQLEnumValueConfigMap,
|
|
26
26
|
GraphQLFieldConfigArgumentMap,
|
|
27
27
|
GraphQLFieldConfigMap,
|
|
28
28
|
GraphQLInputFieldConfigMap
|
|
29
|
-
} from
|
|
30
|
-
import {Context} from
|
|
31
|
-
import {decodeRelayConnectionCursor, RelayConnectionRequest} from
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
29
|
+
} from 'graphql/type/definition'
|
|
30
|
+
import {Context} from '../context'
|
|
31
|
+
import {decodeRelayConnectionCursor, RelayConnectionRequest} from '../ir/connection'
|
|
32
|
+
import {AnyFields} from '../ir/fields'
|
|
33
|
+
import {getConnectionSize, getListSize, getObjectSize} from '../limit.size'
|
|
34
|
+
import {Entity, Interface, JsonObject, Model, Prop} from '../model'
|
|
35
|
+
import {getObject, getUniversalProperties} from '../model.tools'
|
|
36
|
+
import {customScalars} from '../scalars'
|
|
37
|
+
import {ConnectionQuery, CountQuery, EntityByIdQuery, ListQuery, Query} from '../sql/query'
|
|
38
|
+
import {Subscription} from '../subscription'
|
|
39
|
+
import {Limit} from '../util/limit'
|
|
40
|
+
import {getResolveTree, getTreeRequest, hasTreeRequest, simplifyResolveTree} from '../util/resolve-tree'
|
|
41
|
+
import {ensureArray, identity} from '../util/util'
|
|
42
|
+
import {getOrderByMapping, parseOrderBy} from './orderBy'
|
|
43
|
+
import {parseAnyTree, parseObjectTree, parseSqlArguments} from './tree'
|
|
44
|
+
import {parseWhere} from './where'
|
|
42
45
|
|
|
43
46
|
|
|
44
47
|
type GqlFieldMap = GraphQLFieldConfigMap<unknown, Context>
|
|
@@ -109,7 +112,8 @@ export class SchemaBuilder {
|
|
|
109
112
|
return new GraphQLInterfaceType({
|
|
110
113
|
name,
|
|
111
114
|
description: item.description,
|
|
112
|
-
fields: () => this.buildObjectFields(item)
|
|
115
|
+
fields: () => this.buildObjectFields(item),
|
|
116
|
+
resolveType: item.queryable ? (value: any) => value._isTypeOf : undefined
|
|
113
117
|
})
|
|
114
118
|
case "enum":
|
|
115
119
|
return new GraphQLEnumType({
|
|
@@ -143,7 +147,7 @@ export class SchemaBuilder {
|
|
|
143
147
|
type: this.getPropType(prop)
|
|
144
148
|
}
|
|
145
149
|
if (prop.type.kind == 'list-lookup') {
|
|
146
|
-
field.args = this.
|
|
150
|
+
field.args = this.listArguments(prop.type.entity)
|
|
147
151
|
}
|
|
148
152
|
if (object.kind == 'entity' || object.kind == 'object') {
|
|
149
153
|
switch(prop.type.kind) {
|
|
@@ -168,7 +172,7 @@ export class SchemaBuilder {
|
|
|
168
172
|
type = new GraphQLList(this.getPropType(prop.type.item))
|
|
169
173
|
break
|
|
170
174
|
case "fk":
|
|
171
|
-
type = this.get(prop.type.
|
|
175
|
+
type = this.get(prop.type.entity)
|
|
172
176
|
break
|
|
173
177
|
case "lookup":
|
|
174
178
|
return this.get(prop.type.entity)
|
|
@@ -189,46 +193,33 @@ export class SchemaBuilder {
|
|
|
189
193
|
return type
|
|
190
194
|
}
|
|
191
195
|
|
|
192
|
-
private
|
|
196
|
+
private listArguments(typeName: string): GraphQLFieldConfigArgumentMap {
|
|
193
197
|
return {
|
|
194
|
-
where: {type: this.getWhere(
|
|
195
|
-
orderBy: {type: this.getOrderBy(
|
|
198
|
+
where: {type: this.getWhere(typeName)},
|
|
199
|
+
orderBy: {type: this.getOrderBy(typeName)},
|
|
196
200
|
offset: {type: GraphQLInt},
|
|
197
201
|
limit: {type: GraphQLInt}
|
|
198
202
|
}
|
|
199
203
|
}
|
|
200
204
|
|
|
201
|
-
private getWhere(
|
|
202
|
-
let where = this.where.get(
|
|
205
|
+
private getWhere(typeName: string): GraphQLInputType {
|
|
206
|
+
let where = this.where.get(typeName)
|
|
203
207
|
if (where) return where
|
|
204
208
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
case "entity":
|
|
208
|
-
case "object":
|
|
209
|
-
case "union":
|
|
210
|
-
break
|
|
211
|
-
default:
|
|
212
|
-
throw unexpectedCase(object.kind)
|
|
213
|
-
}
|
|
209
|
+
let object = this.model[typeName]
|
|
210
|
+
let properties = getUniversalProperties(this.model, typeName)
|
|
214
211
|
|
|
215
212
|
where = new GraphQLInputObjectType({
|
|
216
|
-
name: `${
|
|
213
|
+
name: `${typeName}WhereInput`,
|
|
217
214
|
fields: () => {
|
|
218
215
|
let fields: GraphQLInputFieldConfigMap = {}
|
|
219
|
-
let properties
|
|
220
|
-
if (object.kind == 'union') {
|
|
221
|
-
properties = getUnionProps(this.model, name).properties
|
|
222
|
-
} else {
|
|
223
|
-
properties = object.properties
|
|
224
|
-
}
|
|
225
216
|
|
|
226
217
|
for (let key in properties) {
|
|
227
218
|
this.buildPropWhereFilters(key, properties[key], fields)
|
|
228
219
|
}
|
|
229
220
|
|
|
230
|
-
if (object.kind == 'entity') {
|
|
231
|
-
let whereList = new GraphQLList(new GraphQLNonNull(this.getWhere(
|
|
221
|
+
if (object.kind == 'entity' || object.kind == 'interface') {
|
|
222
|
+
let whereList = new GraphQLList(new GraphQLNonNull(this.getWhere(typeName)))
|
|
232
223
|
fields['AND'] = {
|
|
233
224
|
type: whereList
|
|
234
225
|
}
|
|
@@ -240,7 +231,8 @@ export class SchemaBuilder {
|
|
|
240
231
|
return fields
|
|
241
232
|
}
|
|
242
233
|
})
|
|
243
|
-
|
|
234
|
+
|
|
235
|
+
this.where.set(typeName, where)
|
|
244
236
|
return where
|
|
245
237
|
}
|
|
246
238
|
|
|
@@ -318,10 +310,8 @@ export class SchemaBuilder {
|
|
|
318
310
|
fields[key] = {type: this.getWhere(prop.type.name)}
|
|
319
311
|
break
|
|
320
312
|
case "fk":
|
|
321
|
-
fields[`${key}_isNull`] = {type: GraphQLBoolean}
|
|
322
|
-
fields[key] = {type: this.getWhere(prop.type.foreignEntity)}
|
|
323
|
-
break
|
|
324
313
|
case "lookup":
|
|
314
|
+
fields[`${key}_isNull`] = {type: GraphQLBoolean}
|
|
325
315
|
fields[key] = {type: this.getWhere(prop.type.entity)}
|
|
326
316
|
break
|
|
327
317
|
case "list-lookup": {
|
|
@@ -353,24 +343,24 @@ export class SchemaBuilder {
|
|
|
353
343
|
return false
|
|
354
344
|
}
|
|
355
345
|
|
|
356
|
-
private getOrderBy(
|
|
357
|
-
let orderBy = this.orderBy.get(
|
|
346
|
+
private getOrderBy(typeName: string): GraphQLInputType {
|
|
347
|
+
let orderBy = this.orderBy.get(typeName)
|
|
358
348
|
if (orderBy) return orderBy
|
|
359
349
|
|
|
360
350
|
let values: GraphQLEnumValueConfigMap = {}
|
|
361
|
-
for (let variant of getOrderByMapping(this.model,
|
|
351
|
+
for (let variant of getOrderByMapping(this.model, typeName).keys()) {
|
|
362
352
|
values[variant] = {}
|
|
363
353
|
}
|
|
364
354
|
|
|
365
355
|
orderBy = new GraphQLList(
|
|
366
356
|
new GraphQLNonNull(
|
|
367
357
|
new GraphQLEnumType({
|
|
368
|
-
name: `${
|
|
358
|
+
name: `${typeName}OrderByInput`,
|
|
369
359
|
values
|
|
370
360
|
})
|
|
371
361
|
)
|
|
372
362
|
)
|
|
373
|
-
this.orderBy.set(
|
|
363
|
+
this.orderBy.set(typeName, orderBy)
|
|
374
364
|
return orderBy
|
|
375
365
|
}
|
|
376
366
|
|
|
@@ -383,11 +373,17 @@ export class SchemaBuilder {
|
|
|
383
373
|
let item = this.model[name]
|
|
384
374
|
switch(item.kind) {
|
|
385
375
|
case "entity":
|
|
386
|
-
this.
|
|
376
|
+
this.installListQuery(name, query, subscription)
|
|
387
377
|
this.installEntityById(name, query, subscription)
|
|
388
378
|
this.installEntityByUniqueInput(name, query)
|
|
389
379
|
this.installRelayConnection(name, query)
|
|
390
380
|
break
|
|
381
|
+
case 'interface':
|
|
382
|
+
if (item.queryable) {
|
|
383
|
+
this.installListQuery(name, query, subscription)
|
|
384
|
+
this.installRelayConnection(name, query)
|
|
385
|
+
}
|
|
386
|
+
break
|
|
391
387
|
}
|
|
392
388
|
}
|
|
393
389
|
|
|
@@ -403,20 +399,21 @@ export class SchemaBuilder {
|
|
|
403
399
|
})
|
|
404
400
|
}
|
|
405
401
|
|
|
406
|
-
private
|
|
402
|
+
private installListQuery(typeName: string, query: GqlFieldMap, subscription: GqlFieldMap): void {
|
|
407
403
|
let model = this.model
|
|
408
|
-
let queryName = toPlural(toCamelCase(
|
|
409
|
-
let outputType = new GraphQLList(new GraphQLNonNull(this.get(
|
|
410
|
-
let argsType = this.
|
|
404
|
+
let queryName = toPlural(toCamelCase(typeName))
|
|
405
|
+
let outputType = new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(this.get(typeName))))
|
|
406
|
+
let argsType = this.listArguments(typeName)
|
|
411
407
|
|
|
412
|
-
function createQuery(context: Context, info: GraphQLResolveInfo) {
|
|
408
|
+
function createQuery(context: Context, info: GraphQLResolveInfo, limit?: Limit) {
|
|
413
409
|
let tree = getResolveTree(info)
|
|
414
|
-
let
|
|
415
|
-
let
|
|
416
|
-
|
|
410
|
+
let args = parseSqlArguments(model, typeName, tree.args)
|
|
411
|
+
let fields = parseAnyTree(model, typeName, info.schema, tree)
|
|
412
|
+
limit?.check(() => getListSize(model, typeName, fields, args.limit, args.where) + 1)
|
|
413
|
+
return new ListQuery(
|
|
417
414
|
model,
|
|
418
415
|
context.openreader.dialect,
|
|
419
|
-
|
|
416
|
+
typeName,
|
|
420
417
|
fields,
|
|
421
418
|
args
|
|
422
419
|
)
|
|
@@ -426,7 +423,7 @@ export class SchemaBuilder {
|
|
|
426
423
|
type: outputType,
|
|
427
424
|
args: argsType,
|
|
428
425
|
resolve(source, args, context, info) {
|
|
429
|
-
let q = createQuery(context, info)
|
|
426
|
+
let q = createQuery(context, info, context.openreader.responseSizeLimit)
|
|
430
427
|
return context.openreader.executeQuery(q)
|
|
431
428
|
}
|
|
432
429
|
}
|
|
@@ -436,7 +433,7 @@ export class SchemaBuilder {
|
|
|
436
433
|
args: argsType,
|
|
437
434
|
resolve: identity,
|
|
438
435
|
subscribe(source, args, context, info) {
|
|
439
|
-
let q = createQuery(context, info)
|
|
436
|
+
let q = createQuery(context, info, context.openreader.subscriptionResponseSizeLimit)
|
|
440
437
|
return context.openreader.subscription(q)
|
|
441
438
|
}
|
|
442
439
|
}
|
|
@@ -449,9 +446,10 @@ export class SchemaBuilder {
|
|
|
449
446
|
id: {type: new GraphQLNonNull(GraphQLString)}
|
|
450
447
|
}
|
|
451
448
|
|
|
452
|
-
function createQuery(context: Context, info: GraphQLResolveInfo) {
|
|
449
|
+
function createQuery(context: Context, info: GraphQLResolveInfo, limit?: Limit) {
|
|
453
450
|
let tree = getResolveTree(info)
|
|
454
|
-
let fields =
|
|
451
|
+
let fields = parseObjectTree(model, entityName, info.schema, tree)
|
|
452
|
+
limit?.check(() => getObjectSize(model, fields) + 1)
|
|
455
453
|
return new EntityByIdQuery(
|
|
456
454
|
model,
|
|
457
455
|
context.openreader.dialect,
|
|
@@ -465,7 +463,7 @@ export class SchemaBuilder {
|
|
|
465
463
|
type: this.get(entityName),
|
|
466
464
|
args: argsType,
|
|
467
465
|
async resolve(source, args, context, info) {
|
|
468
|
-
let q = createQuery(context, info)
|
|
466
|
+
let q = createQuery(context, info, context.openreader.responseSizeLimit)
|
|
469
467
|
return context.openreader.executeQuery(q)
|
|
470
468
|
}
|
|
471
469
|
}
|
|
@@ -475,7 +473,7 @@ export class SchemaBuilder {
|
|
|
475
473
|
args: argsType,
|
|
476
474
|
resolve: identity,
|
|
477
475
|
subscribe(source, args, context, info) {
|
|
478
|
-
let q = createQuery(context, info)
|
|
476
|
+
let q = createQuery(context, info, context.openreader.subscriptionResponseSizeLimit)
|
|
479
477
|
return context.openreader.subscription(q)
|
|
480
478
|
}
|
|
481
479
|
}
|
|
@@ -492,8 +490,9 @@ export class SchemaBuilder {
|
|
|
492
490
|
},
|
|
493
491
|
async resolve(source, args, context, info) {
|
|
494
492
|
let tree = getResolveTree(info)
|
|
495
|
-
let fields =
|
|
496
|
-
|
|
493
|
+
let fields = parseObjectTree(model, entityName, info.schema, tree)
|
|
494
|
+
context.openreader.responseSizeLimit?.check(() => getObjectSize(model, fields) + 1)
|
|
495
|
+
let query = new ListQuery(
|
|
497
496
|
model,
|
|
498
497
|
context.openreader.dialect,
|
|
499
498
|
entityName,
|
|
@@ -507,12 +506,12 @@ export class SchemaBuilder {
|
|
|
507
506
|
}
|
|
508
507
|
}
|
|
509
508
|
|
|
510
|
-
private installRelayConnection(
|
|
509
|
+
private installRelayConnection(typeName: string, query: GqlFieldMap): void {
|
|
511
510
|
let model = this.model
|
|
512
|
-
let outputType = toPlural(
|
|
513
|
-
let edgeType = `${
|
|
511
|
+
let outputType = toPlural(typeName) + 'Connection'
|
|
512
|
+
let edgeType = `${typeName}Edge`
|
|
514
513
|
|
|
515
|
-
query[`${toPlural(toCamelCase(
|
|
514
|
+
query[`${toPlural(toCamelCase(typeName))}Connection`] = {
|
|
516
515
|
type: new GraphQLNonNull(new GraphQLObjectType({
|
|
517
516
|
name: outputType,
|
|
518
517
|
fields: {
|
|
@@ -520,7 +519,7 @@ export class SchemaBuilder {
|
|
|
520
519
|
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(new GraphQLObjectType({
|
|
521
520
|
name: edgeType,
|
|
522
521
|
fields: {
|
|
523
|
-
node: {type: new GraphQLNonNull(this.get(
|
|
522
|
+
node: {type: new GraphQLNonNull(this.get(typeName))},
|
|
524
523
|
cursor: {type: new GraphQLNonNull(GraphQLString)}
|
|
525
524
|
}
|
|
526
525
|
}))))
|
|
@@ -530,10 +529,10 @@ export class SchemaBuilder {
|
|
|
530
529
|
}
|
|
531
530
|
})),
|
|
532
531
|
args: {
|
|
533
|
-
orderBy: {type: new GraphQLNonNull(this.getOrderBy(
|
|
532
|
+
orderBy: {type: new GraphQLNonNull(this.getOrderBy(typeName))},
|
|
534
533
|
after: {type: GraphQLString},
|
|
535
534
|
first: {type: GraphQLInt},
|
|
536
|
-
where: {type: this.getWhere(
|
|
535
|
+
where: {type: this.getWhere(typeName)}
|
|
537
536
|
},
|
|
538
537
|
async resolve(source, args, context, info) {
|
|
539
538
|
let orderByArg = ensureArray(args.orderBy)
|
|
@@ -541,8 +540,8 @@ export class SchemaBuilder {
|
|
|
541
540
|
throw new UserInputError('orderBy argument is required for connection')
|
|
542
541
|
}
|
|
543
542
|
|
|
544
|
-
let req: RelayConnectionRequest = {
|
|
545
|
-
orderBy: parseOrderBy(model,
|
|
543
|
+
let req: RelayConnectionRequest<AnyFields> = {
|
|
544
|
+
orderBy: parseOrderBy(model, typeName, orderByArg),
|
|
546
545
|
where: parseWhere(args.where)
|
|
547
546
|
}
|
|
548
547
|
|
|
@@ -573,22 +572,24 @@ export class SchemaBuilder {
|
|
|
573
572
|
req.edgeCursor = hasTreeRequest(edgeFields, 'cursor')
|
|
574
573
|
let nodeTree = getTreeRequest(edgeFields, 'node')
|
|
575
574
|
if (nodeTree) {
|
|
576
|
-
req.edgeNode =
|
|
575
|
+
req.edgeNode = parseAnyTree(model, typeName, info.schema, nodeTree)
|
|
577
576
|
}
|
|
578
577
|
}
|
|
579
578
|
|
|
580
|
-
|
|
579
|
+
context.openreader.responseSizeLimit?.check(() => getConnectionSize(model, typeName, req) + 1)
|
|
580
|
+
|
|
581
|
+
let result = await context.openreader.executeQuery(new ConnectionQuery(
|
|
581
582
|
model,
|
|
582
583
|
context.openreader.dialect,
|
|
583
|
-
|
|
584
|
+
typeName,
|
|
584
585
|
req
|
|
585
586
|
))
|
|
586
587
|
|
|
587
588
|
if (req.totalCount && result.totalCount == null) {
|
|
588
|
-
result.totalCount = await context.openreader.executeQuery(new
|
|
589
|
+
result.totalCount = await context.openreader.executeQuery(new CountQuery(
|
|
589
590
|
model,
|
|
590
591
|
context.openreader.dialect,
|
|
591
|
-
|
|
592
|
+
typeName,
|
|
592
593
|
req.where
|
|
593
594
|
))
|
|
594
595
|
}
|