@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
@@ -1,5 +1,6 @@
1
- import assert from "assert"
2
- import {Entity, FTS_Query, JsonObject, Model, Prop, PropType} from "./model"
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 i = 0; i < union.variants.length; i++) {
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.foreignEntity != name) {
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.foreignEntity == (b as typeof a).foreignEntity
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
- foreignEntity: Name
112
+ entity: Name
111
113
  }
112
114
 
113
115
 
@@ -1,6 +1,6 @@
1
1
  import assert from "assert"
2
2
  import type {Model} from "../model"
3
- import {getUnionProps} from "../model.tools"
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, entityName: string): OpenCrud_OrderBy_Mapping {
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[entityName]) return cache[entityName]
29
- return cache[entityName] = buildOrderByMapping(model, entityName, 2)
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 object = model[typeName]
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 object.properties) {
42
- let propType = object.properties[key].type
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, entityName: string, input: OpenCrudOrderByValue[]): OrderBy {
74
- let mapping = getOrderByMapping(model, entityName)
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)
@@ -1,7 +1,7 @@
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"
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 "graphql"
23
+ } from 'graphql'
24
24
  import {
25
25
  GraphQLEnumValueConfigMap,
26
26
  GraphQLFieldConfigArgumentMap,
27
27
  GraphQLFieldConfigMap,
28
28
  GraphQLInputFieldConfigMap
29
- } from "graphql/type/definition"
30
- import {Context} from "../context"
31
- import {decodeRelayConnectionCursor, RelayConnectionRequest} from "../ir/connection"
32
- import {Entity, Interface, JsonObject, Model, Prop} from "../model"
33
- import {getObject, getUnionProps} from "../model.tools"
34
- import {customScalars} from "../scalars"
35
- import {EntityByIdQuery, EntityConnectionQuery, EntityCountQuery, EntityListQuery, Query} from "../sql/query"
36
- import {Subscription} from "../subscription"
37
- import {getResolveTree, getTreeRequest, hasTreeRequest, simplifyResolveTree} from "../util/resolve-tree"
38
- import {ensureArray, identity} from "../util/util"
39
- import {getOrderByMapping, parseOrderBy} from "./orderBy"
40
- import {parseEntityListArguments, parseResolveTree} from "./tree"
41
- import {parseWhere} from "./where"
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.entityListArguments(prop.type.entity)
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.foreignEntity)
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 entityListArguments(entityName: string): GraphQLFieldConfigArgumentMap {
196
+ private listArguments(typeName: string): GraphQLFieldConfigArgumentMap {
193
197
  return {
194
- where: {type: this.getWhere(entityName)},
195
- orderBy: {type: this.getOrderBy(entityName)},
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(name: string): GraphQLInputType {
202
- let where = this.where.get(name)
205
+ private getWhere(typeName: string): GraphQLInputType {
206
+ let where = this.where.get(typeName)
203
207
  if (where) return where
204
208
 
205
- const object = this.model[name]
206
- switch(object.kind) {
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: `${name}WhereInput`,
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(name)))
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
- this.where.set(name, where)
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(entityName: string): GraphQLInputType {
357
- let orderBy = this.orderBy.get(entityName)
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, entityName).keys()) {
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: `${entityName}OrderByInput`,
358
+ name: `${typeName}OrderByInput`,
369
359
  values
370
360
  })
371
361
  )
372
362
  )
373
- this.orderBy.set(entityName, orderBy)
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.installEntityList(name, query, subscription)
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 installEntityList(entityName: string, query: GqlFieldMap, subscription: GqlFieldMap): void {
402
+ private installListQuery(typeName: string, query: GqlFieldMap, subscription: GqlFieldMap): void {
407
403
  let model = this.model
408
- let queryName = toPlural(toCamelCase(entityName))
409
- let outputType = new GraphQLList(new GraphQLNonNull(this.get(entityName)))
410
- let argsType = this.entityListArguments(entityName)
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 fields = parseResolveTree(model, entityName, info.schema, tree)
415
- let args = parseEntityListArguments(model, entityName, tree.args)
416
- return new EntityListQuery(
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
- entityName,
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 = parseResolveTree(model, entityName, info.schema, tree)
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 = parseResolveTree(model, entityName, info.schema, tree)
496
- let query = new EntityListQuery(
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(entityName: string, query: GqlFieldMap): void {
509
+ private installRelayConnection(typeName: string, query: GqlFieldMap): void {
511
510
  let model = this.model
512
- let outputType = toPlural(entityName) + 'Connection'
513
- let edgeType = `${entityName}Edge`
511
+ let outputType = toPlural(typeName) + 'Connection'
512
+ let edgeType = `${typeName}Edge`
514
513
 
515
- query[`${toPlural(toCamelCase(entityName))}Connection`] = {
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(entityName))},
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(entityName))},
532
+ orderBy: {type: new GraphQLNonNull(this.getOrderBy(typeName))},
534
533
  after: {type: GraphQLString},
535
534
  first: {type: GraphQLInt},
536
- where: {type: this.getWhere(entityName)}
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, entityName, orderByArg),
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 = parseResolveTree(model, entityName, info.schema, nodeTree)
575
+ req.edgeNode = parseAnyTree(model, typeName, info.schema, nodeTree)
577
576
  }
578
577
  }
579
578
 
580
- let result = await context.openreader.executeQuery(new EntityConnectionQuery(
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
- entityName,
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 EntityCountQuery(
589
+ result.totalCount = await context.openreader.executeQuery(new CountQuery(
589
590
  model,
590
591
  context.openreader.dialect,
591
- entityName,
592
+ typeName,
592
593
  req.where
593
594
  ))
594
595
  }