@subsquid/openreader 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/lib/ir/args.d.ts +1 -1
  2. package/lib/ir/args.d.ts.map +1 -1
  3. package/lib/ir/connection.d.ts +3 -4
  4. package/lib/ir/connection.d.ts.map +1 -1
  5. package/lib/ir/connection.js.map +1 -1
  6. package/lib/ir/fields.d.ts +6 -2
  7. package/lib/ir/fields.d.ts.map +1 -1
  8. package/lib/ir/fields.js +15 -0
  9. package/lib/ir/fields.js.map +1 -1
  10. package/lib/limit.size.d.ts +4 -4
  11. package/lib/limit.size.d.ts.map +1 -1
  12. package/lib/limit.size.js +51 -20
  13. package/lib/limit.size.js.map +1 -1
  14. package/lib/main.js +0 -9
  15. package/lib/main.js.map +1 -1
  16. package/lib/model.d.ts +2 -1
  17. package/lib/model.d.ts.map +1 -1
  18. package/lib/model.schema.d.ts +2 -2
  19. package/lib/model.schema.d.ts.map +1 -1
  20. package/lib/model.schema.js +8 -3
  21. package/lib/model.schema.js.map +1 -1
  22. package/lib/model.tools.d.ts +6 -1
  23. package/lib/model.tools.d.ts.map +1 -1
  24. package/lib/model.tools.js +111 -8
  25. package/lib/model.tools.js.map +1 -1
  26. package/lib/opencrud/orderBy.d.ts +2 -2
  27. package/lib/opencrud/orderBy.d.ts.map +1 -1
  28. package/lib/opencrud/orderBy.js +13 -17
  29. package/lib/opencrud/orderBy.js.map +1 -1
  30. package/lib/opencrud/schema.d.ts +4 -4
  31. package/lib/opencrud/schema.d.ts.map +1 -1
  32. package/lib/opencrud/schema.js +53 -62
  33. package/lib/opencrud/schema.js.map +1 -1
  34. package/lib/opencrud/tree.d.ts +9 -7
  35. package/lib/opencrud/tree.d.ts.map +1 -1
  36. package/lib/opencrud/tree.js +32 -14
  37. package/lib/opencrud/tree.js.map +1 -1
  38. package/lib/sql/cursor.js +2 -2
  39. package/lib/sql/cursor.js.map +1 -1
  40. package/lib/sql/mapping.d.ts +3 -1
  41. package/lib/sql/mapping.d.ts.map +1 -1
  42. package/lib/sql/mapping.js +16 -1
  43. package/lib/sql/mapping.js.map +1 -1
  44. package/lib/sql/printer.d.ts +29 -11
  45. package/lib/sql/printer.d.ts.map +1 -1
  46. package/lib/sql/printer.js +106 -10
  47. package/lib/sql/printer.js.map +1 -1
  48. package/lib/sql/query.d.ts +11 -11
  49. package/lib/sql/query.d.ts.map +1 -1
  50. package/lib/sql/query.js +41 -19
  51. package/lib/sql/query.js.map +1 -1
  52. package/lib/test/queryable.test.d.ts +2 -0
  53. package/lib/test/queryable.test.d.ts.map +1 -0
  54. package/lib/test/queryable.test.js +255 -0
  55. package/lib/test/queryable.test.js.map +1 -0
  56. package/package.json +2 -2
  57. package/src/ir/args.ts +1 -1
  58. package/src/ir/connection.ts +3 -4
  59. package/src/ir/fields.ts +22 -2
  60. package/src/limit.size.ts +55 -30
  61. package/src/main.ts +0 -11
  62. package/src/model.schema.ts +16 -9
  63. package/src/model.tools.ts +121 -8
  64. package/src/model.ts +2 -1
  65. package/src/opencrud/orderBy.ts +13 -17
  66. package/src/opencrud/schema.ts +78 -84
  67. package/src/opencrud/tree.ts +55 -26
  68. package/src/sql/cursor.ts +2 -2
  69. package/src/sql/mapping.ts +18 -1
  70. package/src/sql/printer.ts +137 -21
  71. package/src/sql/query.ts +50 -30
  72. package/src/test/queryable.test.ts +258 -0
package/src/limit.size.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import {unexpectedCase} from '@subsquid/util-internal'
2
+ import assert from 'assert'
2
3
  import {Where} from './ir/args'
3
4
  import {RelayConnectionRequest} from './ir/connection'
4
- import {FieldRequest} from './ir/fields'
5
+ import {AnyFields, FieldRequest} from './ir/fields'
5
6
  import {Model} from './model'
6
- import {getEntity} from './model.tools'
7
+ import {getEntity, getQueryableEntities} from './model.tools'
7
8
 
8
9
 
9
- export function getSize(model: Model, fields: FieldRequest[]): number {
10
+ export function getObjectSize(model: Model, fields: FieldRequest[]): number {
10
11
  let total = 0
11
12
  for (let req of fields) {
12
13
  let size = getFieldSize(model, req)
@@ -31,9 +32,9 @@ function getFieldSize(model: Model, req: FieldRequest): number {
31
32
  case "fk":
32
33
  case "lookup":
33
34
  case "union":
34
- return getSize(model, req.children) + 1
35
+ return getObjectSize(model, req.children) + 1
35
36
  case "list-lookup":
36
- return getEntityListSize(
37
+ return getListSize(
37
38
  model,
38
39
  req.type.entity,
39
40
  req.children,
@@ -46,22 +47,55 @@ function getFieldSize(model: Model, req: FieldRequest): number {
46
47
  }
47
48
 
48
49
 
49
- export function getEntityListSize(
50
+ export function getListSize(
50
51
  model: Model,
51
- entityName: string,
52
- fields: FieldRequest[],
52
+ typeName: string,
53
+ fields: AnyFields,
53
54
  limit?: number,
54
55
  where?: Where
55
56
  ): number {
56
- let cardinality = Math.min(
57
- getEntityCardinality(model, entityName),
58
- limit ?? Infinity,
59
- getWhereCardinality(where)
60
- )
61
- if (Number.isFinite(cardinality)) {
62
- return cardinality * Math.max(getSize(model, fields), 1)
63
- } else {
64
- return Infinity
57
+ let cardinality = getCardinality(model, typeName, limit, where)
58
+ if (!Number.isFinite(cardinality)) return Infinity
59
+ let type = model[typeName]
60
+ switch(type.kind) {
61
+ case 'entity': {
62
+ assert(Array.isArray(fields))
63
+ return cardinality * Math.max(getObjectSize(model, fields), 1)
64
+ }
65
+ case 'interface': {
66
+ assert(!Array.isArray(fields))
67
+ let weight = 1
68
+ for (let entity of getQueryableEntities(model, typeName)) {
69
+ weight = Math.max(weight, getObjectSize(model, fields[entity] || []))
70
+ }
71
+ return cardinality * weight
72
+ }
73
+ default:
74
+ throw unexpectedCase(type.kind)
75
+ }
76
+ }
77
+
78
+
79
+ function getCardinality(
80
+ model: Model,
81
+ typeName: string,
82
+ limit?: number,
83
+ where?: Where
84
+ ): number {
85
+ let type = model[typeName]
86
+ switch(type.kind) {
87
+ case 'entity':
88
+ return Math.min(type.cardinality ?? Infinity, limit ?? Infinity, getWhereCardinality(where))
89
+ case 'interface': {
90
+ let whereCardinality = getWhereCardinality(where)
91
+ let cardinality = 0
92
+ for (let entity of getQueryableEntities(model, typeName)) {
93
+ cardinality += Math.min(getEntity(model, entity).cardinality ?? Infinity, whereCardinality)
94
+ }
95
+ return Math.min(cardinality, limit ?? Infinity)
96
+ }
97
+ default:
98
+ throw unexpectedCase(type.kind)
65
99
  }
66
100
  }
67
101
 
@@ -102,18 +136,14 @@ function getWhereCardinality(where?: Where): number {
102
136
  }
103
137
 
104
138
 
105
- export function getRelaySize(model: Model, entityName: string, req: RelayConnectionRequest): number {
139
+ export function getConnectionSize(model: Model, typeName: string, req: RelayConnectionRequest<AnyFields>): number {
140
+ let first = req.first ?? 100
106
141
  let total = 0
107
- let limit = Math.min(
108
- getEntityCardinality(model, entityName),
109
- req.first ?? 100,
110
- getWhereCardinality(req.where)
111
- )
112
142
  if (req.edgeNode) {
113
- total += limit * Math.max(getSize(model, req.edgeNode), 1)
143
+ total += getListSize(model, typeName, req.edgeNode, first, req.where)
114
144
  }
115
145
  if (req.edgeCursor) {
116
- total += limit
146
+ total += getCardinality(model, typeName, first, req.where)
117
147
  }
118
148
  if (req.pageInfo) {
119
149
  total += 4
@@ -123,8 +153,3 @@ export function getRelaySize(model: Model, entityName: string, req: RelayConnect
123
153
  }
124
154
  return total
125
155
  }
126
-
127
-
128
- function getEntityCardinality(model: Model, entityName: string): number {
129
- return getEntity(model, entityName).cardinality ?? Infinity
130
- }
package/src/main.ts CHANGED
@@ -31,7 +31,6 @@ GraphQL server for postgres-compatible databases
31
31
  program.option('--sql-statement-timeout <ms>', 'sql statement timeout in ms', nat)
32
32
  program.option('--subscriptions', 'enable gql subscriptions')
33
33
  program.option('--subscription-poll-interval <ms>', 'subscription poll interval in ms', nat, 1000)
34
- program.option('--subscription-sql-statement-timeout <ms>', 'sql statement timeout for polling queries', nat)
35
34
  program.option('--subscription-max-response-size <nodes>', 'max response size measured in nodes', nat)
36
35
 
37
36
  let opts = program.parse().opts() as {
@@ -45,7 +44,6 @@ GraphQL server for postgres-compatible databases
45
44
  sqlStatementTimeout?: number
46
45
  subscriptions?: boolean
47
46
  subscriptionPollInterval: number
48
- subscriptionSqlStatementTimeout?: number
49
47
  subscriptionMaxResponseSize?: number
50
48
  }
51
49
 
@@ -56,14 +54,6 @@ GraphQL server for postgres-compatible databases
56
54
  statement_timeout: opts.sqlStatementTimeout || undefined
57
55
  })
58
56
 
59
- let subscriptionConnection: Pool | undefined
60
- if (opts.subscriptions) {
61
- subscriptionConnection = new Pool({
62
- connectionString: opts.dbUrl,
63
- statement_timeout: opts.subscriptionSqlStatementTimeout || opts.sqlStatementTimeout || undefined
64
- })
65
- }
66
-
67
57
  let server = await serve({
68
58
  model,
69
59
  dialect: opts.dbType,
@@ -75,7 +65,6 @@ GraphQL server for postgres-compatible databases
75
65
  maxResponseNodes: opts.maxResponseSize,
76
66
  subscriptions: opts.subscriptions,
77
67
  subscriptionPollInterval: opts.subscriptionPollInterval,
78
- subscriptionConnection,
79
68
  subscriptionMaxResponseNodes: opts.subscriptionMaxResponseSize
80
69
  })
81
70
 
@@ -1,5 +1,5 @@
1
- import {assertNotNull, unexpectedCase} from "@subsquid/util-internal"
2
- import assert from "assert"
1
+ import {assertNotNull, unexpectedCase} from '@subsquid/util-internal'
2
+ import assert from 'assert'
3
3
  import {
4
4
  buildASTSchema,
5
5
  DocumentNode,
@@ -17,14 +17,15 @@ import {
17
17
  GraphQLUnionType,
18
18
  parse,
19
19
  validateSchema
20
- } from "graphql"
21
- import {Index, Model, Prop, PropType, Scalar} from "./model"
22
- import {validateModel} from "./model.tools"
23
- import {customScalars} from "./scalars"
20
+ } from 'graphql'
21
+ import {Index, Model, Prop, PropType, Scalar} from './model'
22
+ import {validateModel} from './model.tools'
23
+ import {customScalars} from './scalars'
24
24
 
25
25
 
26
26
  const baseSchema = buildASTSchema(parse(`
27
27
  directive @entity on OBJECT
28
+ directive @query on INTERFACE
28
29
  directive @derivedFrom(field: String!) on FIELD_DEFINITION
29
30
  directive @unique on FIELD_DEFINITION
30
31
  directive @index(fields: [String!] unique: Boolean) on OBJECT | FIELD_DEFINITION
@@ -88,7 +89,7 @@ function addEntityOrJsonObjectOrInterface(model: Model, type: GraphQLObjectType
88
89
  model[type.name] = {kind, properties, description, interfaces}
89
90
  break
90
91
  case 'interface':
91
- model[type.name] = {kind, properties, description}
92
+ model[type.name] = {kind, properties, description, queryable: isQueryableInterface(type)}
92
93
  break
93
94
  default:
94
95
  throw unexpectedCase(kind)
@@ -173,7 +174,7 @@ function addEntityOrJsonObjectOrInterface(model: Model, type: GraphQLObjectType
173
174
  ...limits
174
175
  }
175
176
  } else if (fieldType instanceof GraphQLObjectType) {
176
- if (isEntityType(fieldType)) {
177
+ if (isEntityType(fieldType) && kind != 'interface') {
177
178
  switch(list.nulls.length) {
178
179
  case 0:
179
180
  if (derivedFrom) {
@@ -196,7 +197,7 @@ function addEntityOrJsonObjectOrInterface(model: Model, type: GraphQLObjectType
196
197
  properties[key] = {
197
198
  type: {
198
199
  kind: 'fk',
199
- foreignEntity: fieldType.name
200
+ entity: fieldType.name
200
201
  },
201
202
  nullable,
202
203
  unique,
@@ -519,6 +520,12 @@ function checkByteWeightDirective(type: GraphQLNamedType, f: GraphQLField<any, a
519
520
  }
520
521
 
521
522
 
523
+ function isQueryableInterface(type: GraphQLOutputType): boolean {
524
+ return type instanceof GraphQLInterfaceType
525
+ && !!type.astNode?.directives?.find(d => d.name.value == 'query')
526
+ }
527
+
528
+
522
529
  function unsupportedFieldTypeError(propName: string): Error {
523
530
  return new SchemaError(`Property ${propName} has unsupported type`)
524
531
  }
@@ -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
@@ -23,6 +23,7 @@ export interface JsonObject extends TypeMeta {
23
23
  export interface Interface extends TypeMeta {
24
24
  kind: 'interface'
25
25
  properties: Record<Name, Prop>
26
+ queryable?: boolean
26
27
  }
27
28
 
28
29
 
@@ -108,7 +109,7 @@ export interface ListPropType {
108
109
 
109
110
  export interface FkPropType {
110
111
  kind: 'fk'
111
- foreignEntity: Name
112
+ entity: Name
112
113
  }
113
114
 
114
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)