@subsquid/openreader 0.2.0 → 0.3.3

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 (128) hide show
  1. package/README.md +2 -15
  2. package/bin/main.js +2 -0
  3. package/dist/db.d.ts +28 -0
  4. package/dist/db.d.ts.map +1 -0
  5. package/dist/db.js +69 -0
  6. package/dist/db.js.map +1 -0
  7. package/dist/gql/opencrud.d.ts +1 -0
  8. package/dist/gql/opencrud.d.ts.map +1 -0
  9. package/dist/gql/opencrud.js +10 -9
  10. package/dist/gql/opencrud.js.map +1 -1
  11. package/dist/gql/schema.d.ts +1 -0
  12. package/dist/gql/schema.d.ts.map +1 -0
  13. package/dist/main.d.ts +2 -3
  14. package/dist/main.d.ts.map +1 -0
  15. package/dist/main.js +6 -30
  16. package/dist/main.js.map +1 -1
  17. package/dist/model.d.ts +1 -0
  18. package/dist/model.d.ts.map +1 -0
  19. package/dist/model.tools.d.ts +1 -0
  20. package/dist/model.tools.d.ts.map +1 -0
  21. package/dist/orderBy.d.ts +1 -0
  22. package/dist/orderBy.d.ts.map +1 -0
  23. package/dist/queryBuilder.d.ts +3 -2
  24. package/dist/queryBuilder.d.ts.map +1 -0
  25. package/dist/queryBuilder.js +28 -27
  26. package/dist/queryBuilder.js.map +1 -1
  27. package/dist/relayConnection.d.ts +1 -0
  28. package/dist/relayConnection.d.ts.map +1 -0
  29. package/dist/requestedFields.d.ts +1 -0
  30. package/dist/requestedFields.d.ts.map +1 -0
  31. package/dist/requestedFields.js +3 -3
  32. package/dist/requestedFields.js.map +1 -1
  33. package/dist/resolver.d.ts +3 -2
  34. package/dist/resolver.d.ts.map +1 -0
  35. package/dist/resolver.js +12 -11
  36. package/dist/resolver.js.map +1 -1
  37. package/dist/scalars.d.ts +2 -2
  38. package/dist/scalars.d.ts.map +1 -0
  39. package/dist/scalars.js +6 -2
  40. package/dist/scalars.js.map +1 -1
  41. package/dist/server.d.ts +5 -11
  42. package/dist/server.d.ts.map +1 -0
  43. package/dist/server.js +13 -31
  44. package/dist/server.js.map +1 -1
  45. package/dist/test/basic.test.d.ts +2 -0
  46. package/dist/test/basic.test.d.ts.map +1 -0
  47. package/dist/test/basic.test.js +286 -0
  48. package/dist/test/basic.test.js.map +1 -0
  49. package/dist/test/connection.test.d.ts +2 -0
  50. package/dist/test/connection.test.d.ts.map +1 -0
  51. package/dist/test/connection.test.js +193 -0
  52. package/dist/test/connection.test.js.map +1 -0
  53. package/dist/test/fts.test.d.ts +2 -0
  54. package/dist/test/fts.test.d.ts.map +1 -0
  55. package/dist/test/fts.test.js +110 -0
  56. package/dist/test/fts.test.js.map +1 -0
  57. package/dist/test/lists.test.d.ts +2 -0
  58. package/dist/test/lists.test.d.ts.map +1 -0
  59. package/dist/test/lists.test.js +266 -0
  60. package/dist/test/lists.test.js.map +1 -0
  61. package/dist/test/lookup.test.d.ts +2 -0
  62. package/dist/test/lookup.test.d.ts.map +1 -0
  63. package/dist/test/lookup.test.js +109 -0
  64. package/dist/test/lookup.test.js.map +1 -0
  65. package/dist/test/scalars.test.d.ts +2 -0
  66. package/dist/test/scalars.test.d.ts.map +1 -0
  67. package/dist/test/scalars.test.js +303 -0
  68. package/dist/test/scalars.test.js.map +1 -0
  69. package/dist/test/tools.test.d.ts +2 -0
  70. package/dist/test/tools.test.d.ts.map +1 -0
  71. package/dist/test/tools.test.js +49 -0
  72. package/dist/test/tools.test.js.map +1 -0
  73. package/dist/test/typed-json.test.d.ts +2 -0
  74. package/dist/test/typed-json.test.d.ts.map +1 -0
  75. package/dist/test/typed-json.test.js +75 -0
  76. package/dist/test/typed-json.test.js.map +1 -0
  77. package/dist/test/unions.test.d.ts +2 -0
  78. package/dist/test/unions.test.d.ts.map +1 -0
  79. package/dist/test/unions.test.js +84 -0
  80. package/dist/test/unions.test.js.map +1 -0
  81. package/dist/test/util/setup.d.ts +7 -0
  82. package/dist/test/util/setup.d.ts.map +1 -0
  83. package/dist/test/util/setup.js +60 -0
  84. package/dist/test/util/setup.js.map +1 -0
  85. package/dist/test/where.test.d.ts +2 -0
  86. package/dist/test/where.test.d.ts.map +1 -0
  87. package/dist/test/where.test.js +127 -0
  88. package/dist/test/where.test.js.map +1 -0
  89. package/dist/tools.d.ts +1 -0
  90. package/dist/tools.d.ts.map +1 -0
  91. package/dist/util.d.ts +1 -13
  92. package/dist/util.d.ts.map +1 -0
  93. package/dist/util.js +6 -79
  94. package/dist/util.js.map +1 -1
  95. package/dist/where.d.ts +1 -0
  96. package/dist/where.d.ts.map +1 -0
  97. package/package.json +26 -20
  98. package/src/db.ts +83 -0
  99. package/src/gql/opencrud.ts +328 -0
  100. package/src/gql/schema.ts +337 -0
  101. package/src/main.ts +51 -0
  102. package/src/model.tools.ts +173 -0
  103. package/src/model.ts +125 -0
  104. package/src/orderBy.ts +105 -0
  105. package/src/queryBuilder.ts +785 -0
  106. package/src/relayConnection.ts +80 -0
  107. package/src/requestedFields.ts +246 -0
  108. package/src/resolver.ts +199 -0
  109. package/src/scalars.ts +247 -0
  110. package/src/server.ts +115 -0
  111. package/src/test/basic.test.ts +339 -0
  112. package/src/test/connection.test.ts +195 -0
  113. package/src/test/fts.test.ts +114 -0
  114. package/src/test/lists.test.ts +278 -0
  115. package/src/test/lookup.test.ts +111 -0
  116. package/src/test/scalars.test.ts +316 -0
  117. package/src/test/tools.test.ts +27 -0
  118. package/src/test/typed-json.test.ts +76 -0
  119. package/src/test/unions.test.ts +85 -0
  120. package/src/test/util/setup.ts +63 -0
  121. package/src/test/where.test.ts +135 -0
  122. package/src/tools.ts +33 -0
  123. package/src/util.ts +39 -0
  124. package/src/where.ts +110 -0
  125. package/CHANGELOG.md +0 -16
  126. package/dist/transaction.d.ts +0 -10
  127. package/dist/transaction.js +0 -47
  128. package/dist/transaction.js.map +0 -1
@@ -0,0 +1,80 @@
1
+ import {UserInputError} from "apollo-server-core"
2
+
3
+
4
+ export interface PageInfo {
5
+ hasNextPage: boolean
6
+ hasPreviousPage: boolean
7
+ startCursor: string
8
+ endCursor: string
9
+ }
10
+
11
+
12
+ export interface ConnectionEdge<T> {
13
+ node?: T
14
+ cursor?: string
15
+ }
16
+
17
+
18
+ export interface ConnectionResponse<T> {
19
+ edges?: ConnectionEdge<T>[]
20
+ pageInfo?: PageInfo
21
+ }
22
+
23
+
24
+ /**
25
+ * Offset value for SQL query
26
+ */
27
+ export type Cursor = number
28
+
29
+
30
+ export function encodeCursor(cursor: Cursor): string {
31
+ return ''+cursor
32
+ }
33
+
34
+
35
+ export function decodeCursor(value: string): Cursor {
36
+ let cursor = parseInt(value)
37
+ if (isFinite(cursor) && cursor >= 0) {
38
+ return cursor
39
+ } else {
40
+ throw new InvalidCursorValue(value)
41
+ }
42
+ }
43
+
44
+
45
+ export class InvalidCursorValue extends UserInputError {
46
+ constructor(value: string) {
47
+ super(`invalid cursor value: ${value}`)
48
+ }
49
+ }
50
+
51
+
52
+ export interface ConnectionArgs {
53
+ first?: number
54
+ after?: string
55
+ }
56
+
57
+
58
+ export interface ConnectionParams {
59
+ offset: number
60
+ limit: number
61
+ }
62
+
63
+
64
+ /**
65
+ * https://relay.dev/assets/files/connections-932f4f2cdffd79724ac76373deb30dc8.htm#sec-Pagination-algorithm
66
+ */
67
+ export function decodeConnectionArgs(args: ConnectionArgs): ConnectionParams {
68
+ let offset = 0
69
+ let limit = 100
70
+ if (args.after) {
71
+ offset = decodeCursor(args.after)
72
+ }
73
+ if (args.first != null) {
74
+ if (args.first < 0) {
75
+ throw new UserInputError("'first' argument of connection can't be less than 0")
76
+ }
77
+ limit = args.first
78
+ }
79
+ return {offset, limit}
80
+ }
@@ -0,0 +1,246 @@
1
+ import {toPlural} from "@subsquid/util"
2
+ import {UserInputError} from "apollo-server-core"
3
+ import assert from "assert"
4
+ import {GraphQLResolveInfo, GraphQLSchema} from "graphql"
5
+ import {
6
+ FieldsByTypeName,
7
+ parseResolveInfo,
8
+ ResolveTree,
9
+ simplifyParsedResolveInfoFragmentWithType
10
+ } from "graphql-parse-resolve-info"
11
+ import {Model, PropType} from "./model"
12
+
13
+
14
+ export interface RequestedFields {
15
+ [name: string]: RequestedField
16
+ }
17
+
18
+
19
+ export interface RequestedField {
20
+ propType: PropType
21
+ requests: FieldRequest[]
22
+ }
23
+
24
+
25
+ export interface FieldRequest {
26
+ alias: string
27
+ children?: RequestedFields
28
+ args?: any
29
+ ifType?: string
30
+ index: number
31
+ }
32
+
33
+
34
+ export function requestedFields(model: Model, entityName: string, info: GraphQLResolveInfo): RequestedFields {
35
+ let tree = getResolveTree(info)
36
+ return collectRequestedFields(model, entityName, info.schema, tree)
37
+ }
38
+
39
+
40
+ function collectRequestedFields(model: Model, objectName: string, schema: GraphQLSchema, tree: ResolveTree): RequestedFields {
41
+ let requested: RequestedFields = {}
42
+ let object = model[objectName]
43
+ assert(object.kind == 'entity' || object.kind == 'object')
44
+
45
+ let fields = simplifyResolveTree(schema, tree, objectName).fields
46
+ for (let alias in fields) {
47
+ let f = fields[alias]
48
+ let prop = object.properties[f.name]
49
+ let propType = prop.type
50
+ switch(propType.kind) {
51
+ case 'scalar':
52
+ case 'enum':
53
+ case 'list':
54
+ requested[f.name] = {
55
+ propType,
56
+ requests: [{alias: f.name, index: 0}]
57
+ }
58
+ break
59
+ case 'object':
60
+ addRequest(requested, f.name, propType, {
61
+ alias,
62
+ children: collectRequestedFields(model, propType.name, schema, f),
63
+ index: 0
64
+ })
65
+ break
66
+ case 'fk':
67
+ addRequest(requested, f.name, propType, {
68
+ alias,
69
+ children: collectRequestedFields(model, propType.foreignEntity, schema, f),
70
+ index: 0
71
+ })
72
+ break
73
+ case 'lookup':
74
+ addRequest(requested, f.name, propType, {
75
+ alias,
76
+ children: collectRequestedFields(model, propType.entity, schema, f),
77
+ index: 0
78
+ })
79
+ break
80
+ case 'list-lookup':
81
+ addRequest(requested, f.name, propType, {
82
+ alias,
83
+ args: f.args,
84
+ children: collectRequestedFields(model, propType.entity, schema, f),
85
+ index: 0
86
+ })
87
+ break
88
+ case 'union':{
89
+ let union = model[propType.name]
90
+ assert(union.kind == 'union')
91
+ let children: RequestedFields = {}
92
+ union.variants.forEach(name => {
93
+ let variantFields = collectRequestedFields(model, name, schema, f)
94
+ for (let key in variantFields) {
95
+ let field = variantFields[key]
96
+ field.requests.forEach(req => {
97
+ addRequest(children, key, field.propType, {...req, ifType: name})
98
+ })
99
+ }
100
+ })
101
+ addRequest(requested, f.name, propType, {
102
+ alias,
103
+ children,
104
+ index: 0
105
+ })
106
+ break
107
+ }
108
+ default:
109
+ throw new Error(`Field ${objectName}.${f.name} has unsupported type and can't be requested`)
110
+ }
111
+ }
112
+
113
+ return requested
114
+ }
115
+
116
+
117
+ function addRequest(requested: RequestedFields, name: string, propType: PropType, req: FieldRequest): void {
118
+ let field = requested[name]
119
+ if (field == null) {
120
+ requested[name] = {
121
+ propType,
122
+ requests: [req]
123
+ }
124
+ } else {
125
+ field.requests.push(req)
126
+ }
127
+ }
128
+
129
+
130
+ export interface ConnectionRequestedFields {
131
+ totalCount?: boolean
132
+ pageInfo?: boolean
133
+ edges?: {
134
+ node?: RequestedFields
135
+ cursor?: boolean
136
+ }
137
+ }
138
+
139
+
140
+ export function connectionRequestedFields(model: Model, entityName: string, info: GraphQLResolveInfo): ConnectionRequestedFields {
141
+ let requested: ConnectionRequestedFields = {}
142
+ let tree = getResolveTree(info, toPlural(entityName) + 'Connection')
143
+ requested.totalCount = hasTreeRequest(tree.fields, 'totalCount')
144
+ requested.pageInfo = hasTreeRequest(tree.fields, 'pageInfo')
145
+ let edgesTree = getTreeRequest(tree.fields, 'edges')
146
+ if (edgesTree) {
147
+ let edgeFields = simplifyResolveTree(info.schema, edgesTree, entityName + 'Edge').fields
148
+ requested.edges = {}
149
+ requested.edges.cursor = hasTreeRequest(edgeFields, 'cursor')
150
+ let nodeTree = getTreeRequest(edgeFields, 'node')
151
+ if (nodeTree) {
152
+ requested.edges.node = collectRequestedFields(model, entityName, info.schema, nodeTree)
153
+ }
154
+ }
155
+ return requested
156
+ }
157
+
158
+
159
+ export interface FtsRequestedFields {
160
+ item?: Record<string, RequestedFields>
161
+ highlight?: boolean
162
+ rank?: boolean
163
+ }
164
+
165
+
166
+ export function ftsRequestedFields(model: Model, queryName: string, info: GraphQLResolveInfo): FtsRequestedFields {
167
+ let query = model[queryName]
168
+ assert(query.kind == 'fts')
169
+
170
+ let requested: FtsRequestedFields = {}
171
+ let tree = getResolveTree(info, queryName + '_Output')
172
+
173
+ requested.rank = hasTreeRequest(tree.fields, 'rank')
174
+ requested.highlight = hasTreeRequest(tree.fields, 'highlight')
175
+
176
+ let itemTree = getTreeRequest(tree.fields, 'item')
177
+ if (itemTree) {
178
+ requested.item = {}
179
+ for (let i = 0; i < query.sources.length; i++) {
180
+ let entity = query.sources[i].entity
181
+ let fields = collectRequestedFields(model, entity, info.schema, itemTree)
182
+ for (let key in fields) {
183
+ requested.item[entity] = fields
184
+ break
185
+ }
186
+ }
187
+ }
188
+
189
+ return requested
190
+ }
191
+
192
+
193
+ function getTreeRequest(treeFields: ResolveTreeFields, fieldName: string): ResolveTree | undefined {
194
+ let req: ResolveTree | undefined
195
+ for (let alias in treeFields) {
196
+ let e = treeFields[alias]
197
+ if (e.name != fieldName) continue
198
+ if (req != null) throw new UserInputError(`multiple aliases for field '${fieldName}' are not supported`)
199
+ req = e
200
+ }
201
+ return req
202
+ }
203
+
204
+
205
+ function hasTreeRequest(treeFields: ResolveTreeFields, fieldName: string): boolean {
206
+ for (let alias in treeFields) {
207
+ let e = treeFields[alias]
208
+ if (e.name == fieldName) return true
209
+ }
210
+ return false
211
+ }
212
+
213
+
214
+ type ResolveTreeFields = {
215
+ [alias: string]: ResolveTree
216
+ }
217
+
218
+
219
+ interface ResolveTreeWithFields extends ResolveTree {
220
+ fields: ResolveTreeFields
221
+ }
222
+
223
+
224
+ function getResolveTree(info: GraphQLResolveInfo): ResolveTree
225
+ function getResolveTree(info: GraphQLResolveInfo, typeName: string): ResolveTreeWithFields
226
+ function getResolveTree(info: GraphQLResolveInfo, typeName?: string): ResolveTree {
227
+ let tree = parseResolveInfo(info)
228
+ assert(isResolveTree(tree))
229
+ if (typeName) {
230
+ return simplifyResolveTree(info.schema, tree, typeName)
231
+ } else {
232
+ return tree
233
+ }
234
+ }
235
+
236
+
237
+ function simplifyResolveTree(schema: GraphQLSchema, tree: ResolveTree, typeName: string): ResolveTreeWithFields {
238
+ let type = schema.getType(typeName)
239
+ assert(type != null)
240
+ return simplifyParsedResolveInfoFragmentWithType(tree, type)
241
+ }
242
+
243
+
244
+ function isResolveTree(resolveInfo: ResolveTree | FieldsByTypeName | null | undefined): resolveInfo is ResolveTree {
245
+ return resolveInfo != null && resolveInfo.fieldsByTypeName != null
246
+ }
@@ -0,0 +1,199 @@
1
+ import type {IFieldResolver, IResolvers} from "@graphql-tools/utils"
2
+ import {toCamelCase} from "@subsquid/util"
3
+ import {UserInputError} from "apollo-server-core"
4
+ import assert from "assert"
5
+ import type {GraphQLResolveInfo} from "graphql"
6
+ import type {Database, Transaction} from "./db"
7
+ import type {Entity, JsonObject, Model} from "./model"
8
+ import {QueryBuilder} from "./queryBuilder"
9
+ import {
10
+ ConnectionArgs as RelayConnectionArgs,
11
+ ConnectionEdge,
12
+ ConnectionResponse as RelayConnectionResponse,
13
+ decodeConnectionArgs,
14
+ encodeCursor,
15
+ PageInfo
16
+ } from "./relayConnection"
17
+ import {connectionRequestedFields, ftsRequestedFields, requestedFields} from "./requestedFields"
18
+ import {getScalarResolvers} from "./scalars"
19
+ import {ensureArray, toQueryListField, unsupportedCase} from "./util"
20
+
21
+
22
+ export interface ResolverContext {
23
+ openReaderTransaction: Transaction
24
+ }
25
+
26
+
27
+ export function buildResolvers(model: Model): IResolvers<unknown, ResolverContext> {
28
+ let Query: Record<string, IFieldResolver<unknown, ResolverContext>> = {}
29
+ let resolvers: IResolvers = {Query, ...getScalarResolvers()}
30
+
31
+ for (let name in model) {
32
+ let item = model[name]
33
+ switch(item.kind) {
34
+ case 'entity':
35
+ Query[toQueryListField(name)] = async (source, args, context, info) => {
36
+ let fields = requestedFields(model, name, info)
37
+ let db = await context.openReaderTransaction.get()
38
+ return new QueryBuilder(model, db).executeSelect(name, args, fields)
39
+ }
40
+ Query[toQueryListField(name) + 'Connection'] = async (source, args, context, info) => {
41
+ let db = await context.openReaderTransaction.get()
42
+ return resolveEntityConnection(model, name, args, info, db)
43
+ }
44
+ Query[`${toCamelCase(name)}ById`] = async (source, args, context, info) => {
45
+ let fields = requestedFields(model, name, info)
46
+ let db = await context.openReaderTransaction.get()
47
+ let result = await new QueryBuilder(model, db)
48
+ .executeSelect(name, {where: {id_eq: args.id}}, fields)
49
+ assert(result.length < 2)
50
+ return result[0]
51
+ }
52
+ Query[`${toCamelCase(name)}ByUniqueInput`] = async (source, args, context, info) => {
53
+ let fields = requestedFields(model, name, info)
54
+ let db = await context.openReaderTransaction.get()
55
+ let result = await new QueryBuilder(model, db)
56
+ .executeSelect(name, {where: {id_eq: args.where.id}}, fields)
57
+ assert(result.length < 2)
58
+ return result[0]
59
+ }
60
+ installFieldResolvers(name, item)
61
+ break
62
+ case 'object':
63
+ installFieldResolvers(name, item)
64
+ break
65
+ case 'union':
66
+ resolvers[name] = {
67
+ __resolveType: resolveUnionType
68
+ }
69
+ break
70
+ case 'fts':
71
+ Query[name] = async (source, args, context, info) => {
72
+ let fields = ftsRequestedFields(model, name, info)
73
+ let db = await context.openReaderTransaction.get()
74
+ return new QueryBuilder(model, db).executeFulltextSearch(name, args, fields)
75
+ }
76
+ resolvers[`${name}_Item`] = {
77
+ __resolveType: resolveUnionType
78
+ }
79
+ break
80
+ }
81
+ }
82
+
83
+ function installFieldResolvers(name: string, object: Entity | JsonObject): void {
84
+ let fields: Record<string, IFieldResolver<any, any>> = {}
85
+ for (let key in object.properties) {
86
+ let kind = object.properties[key].type.kind
87
+ switch(kind) {
88
+ case 'object':
89
+ case 'union':
90
+ case 'fk':
91
+ case 'lookup':
92
+ case 'list-lookup':
93
+ fields[key] = aliasResolver
94
+ break
95
+ case 'scalar':
96
+ case 'enum':
97
+ case 'list':
98
+ break
99
+ default:
100
+ throw unsupportedCase(kind)
101
+ }
102
+ }
103
+ resolvers[name] = fields
104
+ }
105
+
106
+ return resolvers
107
+ }
108
+
109
+
110
+ function resolveUnionType(source: any): string {
111
+ return source.isTypeOf
112
+ }
113
+
114
+
115
+ function aliasResolver(source: any, args: unknown, ctx: unknown, info: GraphQLResolveInfo): any {
116
+ return source[info.path.key]
117
+ }
118
+
119
+
120
+ interface ConnectionArgs extends RelayConnectionArgs {
121
+ orderBy?: string[]
122
+ where?: any
123
+ }
124
+
125
+
126
+ interface ConnectionResponse extends RelayConnectionResponse<any> {
127
+ totalCount?: number
128
+ }
129
+
130
+
131
+ async function resolveEntityConnection(
132
+ model: Model,
133
+ entityName: string,
134
+ args: ConnectionArgs,
135
+ info: GraphQLResolveInfo,
136
+ db: Database
137
+ ): Promise<ConnectionResponse> {
138
+ let response: ConnectionResponse = {}
139
+
140
+ let orderBy = args.orderBy && ensureArray(args.orderBy)
141
+ if (!orderBy?.length) {
142
+ throw new UserInputError('orderBy argument is required for connection')
143
+ }
144
+
145
+ let {offset, limit} = decodeConnectionArgs(args)
146
+ let listArgs = {
147
+ where: args.where,
148
+ orderBy,
149
+ offset,
150
+ limit: limit + 1
151
+ }
152
+
153
+ // https://relay.dev/assets/files/connections-932f4f2cdffd79724ac76373deb30dc8.htm#sec-undefined.PageInfo.Fields
154
+ function pageInfo(listLength: number): PageInfo {
155
+ return {
156
+ hasNextPage: listLength > limit,
157
+ hasPreviousPage: listLength > 0 && offset > 0,
158
+ startCursor: listLength > 0 ? encodeCursor(offset + 1) : '',
159
+ endCursor: listLength > 0 ? encodeCursor(offset + Math.min(limit, listLength)) : ''
160
+ }
161
+ }
162
+
163
+ let fields = connectionRequestedFields(model, entityName, info)
164
+ if (fields.edges?.node) {
165
+ let nodes = await new QueryBuilder(model, db).executeSelect(entityName, listArgs, fields.edges.node)
166
+ let edges: ConnectionEdge<any>[] = new Array(Math.min(limit, nodes.length))
167
+ for (let i = 0; i < edges.length; i++) {
168
+ edges[i] = {
169
+ node: nodes[i],
170
+ cursor: encodeCursor(offset + i + 1)
171
+ }
172
+ }
173
+ response.edges = edges
174
+ response.pageInfo = pageInfo(nodes.length)
175
+ if (nodes.length > 0 && nodes.length <= limit) {
176
+ response.totalCount = offset + nodes.length
177
+ }
178
+ } else if (fields.edges?.cursor || fields.pageInfo) {
179
+ let listLength = await new QueryBuilder(model, db).executeListCount(entityName, listArgs)
180
+ response.pageInfo = pageInfo(listLength)
181
+ if (fields.edges?.cursor) {
182
+ response.edges = []
183
+ for (let i = 0; i < Math.min(limit, listLength); i++) {
184
+ response.edges.push({
185
+ cursor: encodeCursor(offset + i + 1)
186
+ })
187
+ }
188
+ }
189
+ if (listLength > 0 && listLength <= limit) {
190
+ response.totalCount = offset + listLength
191
+ }
192
+ }
193
+
194
+ if (fields.totalCount && response.totalCount == null) {
195
+ response.totalCount = await new QueryBuilder(model, db).executeSelectCount(entityName, listArgs.where)
196
+ }
197
+
198
+ return response
199
+ }