@subsquid/openreader 0.3.2 → 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 (104) hide show
  1. package/dist/db.d.ts +1 -0
  2. package/dist/db.d.ts.map +1 -0
  3. package/dist/gql/opencrud.d.ts +1 -0
  4. package/dist/gql/opencrud.d.ts.map +1 -0
  5. package/dist/gql/schema.d.ts +1 -0
  6. package/dist/gql/schema.d.ts.map +1 -0
  7. package/dist/main.d.ts +1 -0
  8. package/dist/main.d.ts.map +1 -0
  9. package/dist/model.d.ts +1 -0
  10. package/dist/model.d.ts.map +1 -0
  11. package/dist/model.tools.d.ts +1 -0
  12. package/dist/model.tools.d.ts.map +1 -0
  13. package/dist/orderBy.d.ts +1 -0
  14. package/dist/orderBy.d.ts.map +1 -0
  15. package/dist/queryBuilder.d.ts +1 -0
  16. package/dist/queryBuilder.d.ts.map +1 -0
  17. package/dist/relayConnection.d.ts +1 -0
  18. package/dist/relayConnection.d.ts.map +1 -0
  19. package/dist/requestedFields.d.ts +1 -0
  20. package/dist/requestedFields.d.ts.map +1 -0
  21. package/dist/resolver.d.ts +1 -0
  22. package/dist/resolver.d.ts.map +1 -0
  23. package/dist/scalars.d.ts +1 -0
  24. package/dist/scalars.d.ts.map +1 -0
  25. package/dist/server.d.ts +1 -0
  26. package/dist/server.d.ts.map +1 -0
  27. package/dist/test/basic.test.d.ts +2 -0
  28. package/dist/test/basic.test.d.ts.map +1 -0
  29. package/dist/test/basic.test.js +286 -0
  30. package/dist/test/basic.test.js.map +1 -0
  31. package/dist/test/connection.test.d.ts +2 -0
  32. package/dist/test/connection.test.d.ts.map +1 -0
  33. package/dist/test/connection.test.js +193 -0
  34. package/dist/test/connection.test.js.map +1 -0
  35. package/dist/test/fts.test.d.ts +2 -0
  36. package/dist/test/fts.test.d.ts.map +1 -0
  37. package/dist/test/fts.test.js +110 -0
  38. package/dist/test/fts.test.js.map +1 -0
  39. package/dist/test/lists.test.d.ts +2 -0
  40. package/dist/test/lists.test.d.ts.map +1 -0
  41. package/dist/test/lists.test.js +266 -0
  42. package/dist/test/lists.test.js.map +1 -0
  43. package/dist/test/lookup.test.d.ts +2 -0
  44. package/dist/test/lookup.test.d.ts.map +1 -0
  45. package/dist/test/lookup.test.js +109 -0
  46. package/dist/test/lookup.test.js.map +1 -0
  47. package/dist/test/scalars.test.d.ts +2 -0
  48. package/dist/test/scalars.test.d.ts.map +1 -0
  49. package/dist/test/scalars.test.js +303 -0
  50. package/dist/test/scalars.test.js.map +1 -0
  51. package/dist/test/tools.test.d.ts +2 -0
  52. package/dist/test/tools.test.d.ts.map +1 -0
  53. package/dist/test/tools.test.js +49 -0
  54. package/dist/test/tools.test.js.map +1 -0
  55. package/dist/test/typed-json.test.d.ts +2 -0
  56. package/dist/test/typed-json.test.d.ts.map +1 -0
  57. package/dist/test/typed-json.test.js +75 -0
  58. package/dist/test/typed-json.test.js.map +1 -0
  59. package/dist/test/unions.test.d.ts +2 -0
  60. package/dist/test/unions.test.d.ts.map +1 -0
  61. package/dist/test/unions.test.js +84 -0
  62. package/dist/test/unions.test.js.map +1 -0
  63. package/dist/test/util/setup.d.ts +7 -0
  64. package/dist/test/util/setup.d.ts.map +1 -0
  65. package/dist/test/util/setup.js +60 -0
  66. package/dist/test/util/setup.js.map +1 -0
  67. package/dist/test/where.test.d.ts +2 -0
  68. package/dist/test/where.test.d.ts.map +1 -0
  69. package/dist/test/where.test.js +127 -0
  70. package/dist/test/where.test.js.map +1 -0
  71. package/dist/tools.d.ts +1 -0
  72. package/dist/tools.d.ts.map +1 -0
  73. package/dist/util.d.ts +1 -0
  74. package/dist/util.d.ts.map +1 -0
  75. package/dist/where.d.ts +1 -0
  76. package/dist/where.d.ts.map +1 -0
  77. package/package.json +4 -3
  78. package/src/db.ts +83 -0
  79. package/src/gql/opencrud.ts +328 -0
  80. package/src/gql/schema.ts +337 -0
  81. package/src/main.ts +51 -0
  82. package/src/model.tools.ts +173 -0
  83. package/src/model.ts +125 -0
  84. package/src/orderBy.ts +105 -0
  85. package/src/queryBuilder.ts +785 -0
  86. package/src/relayConnection.ts +80 -0
  87. package/src/requestedFields.ts +246 -0
  88. package/src/resolver.ts +199 -0
  89. package/src/scalars.ts +247 -0
  90. package/src/server.ts +115 -0
  91. package/src/test/basic.test.ts +339 -0
  92. package/src/test/connection.test.ts +195 -0
  93. package/src/test/fts.test.ts +114 -0
  94. package/src/test/lists.test.ts +278 -0
  95. package/src/test/lookup.test.ts +111 -0
  96. package/src/test/scalars.test.ts +316 -0
  97. package/src/test/tools.test.ts +27 -0
  98. package/src/test/typed-json.test.ts +76 -0
  99. package/src/test/unions.test.ts +85 -0
  100. package/src/test/util/setup.ts +63 -0
  101. package/src/test/where.test.ts +135 -0
  102. package/src/tools.ts +33 -0
  103. package/src/util.ts +39 -0
  104. package/src/where.ts +110 -0
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const setup_1 = require("./util/setup");
4
+ describe('AND, OR on entity filters', function () {
5
+ (0, setup_1.useDatabase)([
6
+ `create table item (id text primary key, a int, b int)`,
7
+ `insert into item (id, a, b) values ('1', 1, 1)`,
8
+ `insert into item (id, a, b) values ('2', 2, 2)`,
9
+ `insert into item (id, a, b) values ('3', 3, 2)`,
10
+ `insert into item (id, a, b) values ('4', 4, 4)`,
11
+ `insert into item (id, a, b) values ('5', 5, 4)`,
12
+ `insert into item (id, a, b) values ('6', 5, 6)`,
13
+ ]);
14
+ const client = (0, setup_1.useServer)(`
15
+ type Item @entity {
16
+ id: ID!
17
+ a: Int
18
+ b: Int
19
+ }
20
+ `);
21
+ it('{c, and: {c}}', function () {
22
+ return client.test(`
23
+ query {
24
+ items(where: {a_eq: 1, AND: {b_eq: 1}} orderBy: id_ASC) { id }
25
+ }
26
+ `, {
27
+ items: [
28
+ { id: '1' }
29
+ ]
30
+ });
31
+ });
32
+ it('{and: {and: {c}, c}}', function () {
33
+ return client.test(`
34
+ query {
35
+ items(where: {AND: {b_eq: 2, AND: {a_eq: 3}}} orderBy: id_ASC) { id }
36
+ }
37
+ `, {
38
+ items: [
39
+ { id: '3' }
40
+ ]
41
+ });
42
+ });
43
+ it('{and: [{c}, {c}]}', function () {
44
+ return client.test(`
45
+ query {
46
+ items(where: {AND: [{a_eq: 2}, {b_eq: 2}]} orderBy: id_ASC) { id }
47
+ }
48
+ `, {
49
+ items: [
50
+ { id: '2' }
51
+ ]
52
+ });
53
+ });
54
+ it('{c, {or: {c}}}', function () {
55
+ return client.test(`
56
+ query {
57
+ items(where: {a_eq: 1, OR: {a_eq: 2}} orderBy: id_ASC) { id }
58
+ }
59
+ `, {
60
+ items: [
61
+ { id: '1' },
62
+ { id: '2' }
63
+ ]
64
+ });
65
+ });
66
+ it('{or: [{c}, {c}]}', function () {
67
+ return client.test(`
68
+ query {
69
+ items(where: {OR: [{a_eq: 2}, {a_eq: 3}]} orderBy: id_ASC) { id }
70
+ }
71
+ `, {
72
+ items: [
73
+ { id: '2' },
74
+ { id: '3' }
75
+ ]
76
+ });
77
+ });
78
+ it('{or: {or: {c}, c}}', function () {
79
+ return client.test(`
80
+ query {
81
+ items(where: {OR: {a_eq: 1, OR: {b_eq: 2}}} orderBy: id_ASC) { id }
82
+ }
83
+ `, {
84
+ items: [
85
+ { id: '1' },
86
+ { id: '2' },
87
+ { id: '3' }
88
+ ]
89
+ });
90
+ });
91
+ it('{and: [{or: {c}, c}, {or: {c}, c}]}', function () {
92
+ return client.test(`
93
+ query {
94
+ items(where: {AND: [{OR: {a_eq: 5}, a_eq: 4}, {OR: {b_eq: 2}, b_eq: 4}]} orderBy: id_ASC) { id }
95
+ }
96
+ `, {
97
+ items: [
98
+ { id: '4' },
99
+ { id: '5' }
100
+ ]
101
+ });
102
+ });
103
+ it('{c, and: {c}, or: {c}}', function () {
104
+ return client.test(`
105
+ query {
106
+ items(where: { a_eq: 4, AND: {b_eq: 4}, OR: {b_eq: 6} } orderBy: id_ASC) { id }
107
+ }
108
+ `, {
109
+ items: [
110
+ { id: '4' },
111
+ { id: '6' }
112
+ ]
113
+ });
114
+ });
115
+ it('handles empty wheres', function () {
116
+ return client.test(`
117
+ query {
118
+ items(where: { a_eq: 4, AND: { OR: {}, AND: {} }, OR: { OR: {AND: {} } } } orderBy: id_ASC) { id }
119
+ }
120
+ `, {
121
+ items: [
122
+ { id: '4' }
123
+ ]
124
+ });
125
+ });
126
+ });
127
+ //# sourceMappingURL=where.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"where.test.js","sourceRoot":"","sources":["../../src/test/where.test.ts"],"names":[],"mappings":";;AAAA,wCAAmD;AAEnD,QAAQ,CAAC,2BAA2B,EAAE;IAClC,IAAA,mBAAW,EAAC;QACR,uDAAuD;QACvD,gDAAgD;QAChD,gDAAgD;QAChD,gDAAgD;QAChD,gDAAgD;QAChD,gDAAgD;QAChD,gDAAgD;KACnD,CAAC,CAAA;IAEF,MAAM,MAAM,GAAG,IAAA,iBAAS,EAAC;;;;;;KAMxB,CAAC,CAAA;IAEF,EAAE,CAAC,eAAe,EAAE;QAChB,OAAO,MAAM,CAAC,IAAI,CAAC;;;;SAIlB,EAAE;YACC,KAAK,EAAE;gBACH,EAAC,EAAE,EAAE,GAAG,EAAC;aACZ;SACJ,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sBAAsB,EAAE;QACvB,OAAO,MAAM,CAAC,IAAI,CAAC;;;;SAIlB,EAAE;YACC,KAAK,EAAE;gBACH,EAAC,EAAE,EAAE,GAAG,EAAC;aACZ;SACJ,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mBAAmB,EAAE;QACpB,OAAO,MAAM,CAAC,IAAI,CAAC;;;;SAIlB,EAAE;YACC,KAAK,EAAE;gBACH,EAAC,EAAE,EAAE,GAAG,EAAC;aACZ;SACJ,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gBAAgB,EAAE;QACjB,OAAO,MAAM,CAAC,IAAI,CAAC;;;;SAIlB,EAAE;YACC,KAAK,EAAE;gBACH,EAAC,EAAE,EAAE,GAAG,EAAC;gBACT,EAAC,EAAE,EAAE,GAAG,EAAC;aACZ;SACJ,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kBAAkB,EAAE;QACnB,OAAO,MAAM,CAAC,IAAI,CAAC;;;;SAIlB,EAAE;YACC,KAAK,EAAE;gBACH,EAAC,EAAE,EAAE,GAAG,EAAC;gBACT,EAAC,EAAE,EAAE,GAAG,EAAC;aACZ;SACJ,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oBAAoB,EAAE;QACrB,OAAO,MAAM,CAAC,IAAI,CAAC;;;;SAIlB,EAAE;YACC,KAAK,EAAE;gBACH,EAAC,EAAE,EAAE,GAAG,EAAC;gBACT,EAAC,EAAE,EAAE,GAAG,EAAC;gBACT,EAAC,EAAE,EAAE,GAAG,EAAC;aACZ;SACJ,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE;QACtC,OAAO,MAAM,CAAC,IAAI,CAAC;;;;SAIlB,EAAE;YACC,KAAK,EAAE;gBACH,EAAC,EAAE,EAAE,GAAG,EAAC;gBACT,EAAC,EAAE,EAAE,GAAG,EAAC;aACZ;SACJ,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wBAAwB,EAAE;QACzB,OAAO,MAAM,CAAC,IAAI,CAAC;;;;SAIlB,EAAE;YACC,KAAK,EAAE;gBACH,EAAC,EAAE,EAAE,GAAG,EAAC;gBACT,EAAC,EAAE,EAAE,GAAG,EAAC;aACZ;SACJ,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sBAAsB,EAAE;QACvB,OAAO,MAAM,CAAC,IAAI,CAAC;;;;SAIlB,EAAE;YACC,KAAK,EAAE;gBACH,EAAC,EAAE,EAAE,GAAG,EAAC;aACZ;SACJ,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;AACN,CAAC,CAAC,CAAA"}
package/dist/tools.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import type { Model } from "./model";
2
2
  export declare function loadModel(schemaFile: string): Model;
3
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,SAAS,CAAA;AAGlC,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,CAwBnD"}
package/dist/util.d.ts CHANGED
@@ -5,3 +5,4 @@ export declare function toTable(entityName: string): string;
5
5
  export declare function ensureArray<T>(item: T | T[]): T[];
6
6
  export declare function unsupportedCase(value: string): Error;
7
7
  export declare function toInt(val: number | string): number;
8
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAIA,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAGD,wBAAgB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErD;AAGD,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEvD;AAGD,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAElD;AAGD,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,CAEjD;AAGD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAEpD;AAGD,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAIlD"}
package/dist/where.d.ts CHANGED
@@ -6,3 +6,4 @@ export declare function parseWhereField(field: string): {
6
6
  };
7
7
  export declare function hasConditions(where?: any): where is any;
8
8
  export declare function whereOpToSqlOperator(op: WhereOp): string;
9
+ //# sourceMappingURL=where.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"where.d.ts","sourceRoot":"","sources":["../src/where.ts"],"names":[],"mappings":"AACA,oBAAY,OAAO,GACf,GAAG,GAAG,cAAc;AACpB,IAAI,GAAG,QAAQ,GACf,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GAAG,QAAQ,GACf,UAAU,GAAG,cAAc,GAC3B,YAAY,GAAG,gBAAgB,GAC/B,UAAU,GAAG,cAAc,GAC3B,aAAa,GACb,aAAa,GACb,cAAc,GACd,MAAM,GACN,OAAO,GACP,MAAM,CAAA;AAmCV,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,CAQ3E;AAGD,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,GAAG,GAAG,KAAK,IAAI,GAAG,CAsBvD;AAGD,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAqBxD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subsquid/openreader",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "GraphQL server for squid framework",
5
5
  "keywords": [
6
6
  "graphql",
@@ -15,14 +15,15 @@
15
15
  "openreader": "./bin/main.js"
16
16
  },
17
17
  "files": [
18
+ "bin",
18
19
  "dist",
19
- "!dist/test"
20
+ "src"
20
21
  ],
21
22
  "dependencies": {
22
23
  "@graphql-tools/merge": "^8",
23
24
  "@graphql-tools/utils": "^8",
24
25
  "@subsquid/graphiql-console": "^0.2.0",
25
- "@subsquid/util": "^0.0.2",
26
+ "@subsquid/util": "^0.0.3",
26
27
  "apollo-server-core": "^3.5.0",
27
28
  "apollo-server-express": "^3.5.0",
28
29
  "express": "^4.17.2",
package/src/db.ts ADDED
@@ -0,0 +1,83 @@
1
+ import type {ClientBase, Pool, PoolClient, PoolConfig} from "pg"
2
+
3
+
4
+ export interface Database {
5
+ query(sql: string, parameters?: any[]): Promise<any[][]>
6
+ escapeIdentifier(name: string): string
7
+ }
8
+
9
+
10
+ /**
11
+ * This is an interface OpenReader uses to interact with underling database.
12
+ */
13
+ export interface Transaction {
14
+ get(): Promise<Database>
15
+ }
16
+
17
+
18
+ export class PgDatabase implements Database {
19
+ constructor(private client: ClientBase) {}
20
+
21
+ query(sql: string, parameters?: any[]): Promise<any[]> {
22
+ return this.client.query({text: sql, rowMode: 'array'}, parameters).then(result => result.rows)
23
+ }
24
+
25
+ escapeIdentifier(name: string): string {
26
+ return this.client.escapeIdentifier(name)
27
+ }
28
+ }
29
+
30
+
31
+ export class PoolTransaction implements Transaction {
32
+ private tx: Promise<{client: PoolClient, db: Database}> | undefined
33
+ private closed = false
34
+
35
+ constructor(private pool: Pool) {}
36
+
37
+ async get(): Promise<Database> {
38
+ if (this.closed) {
39
+ throw new Error('Too late to request transaction')
40
+ }
41
+ this.tx = this.tx || this.startTransaction()
42
+ let {db} = await this.tx
43
+ return db
44
+ }
45
+
46
+ private async startTransaction(): Promise<{client: PoolClient, db: Database}> {
47
+ let client = await this.pool.connect()
48
+ try {
49
+ await client.query('START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY')
50
+ return {
51
+ client,
52
+ db: new PgDatabase(client)
53
+ }
54
+ } catch(e: any) {
55
+ client.release()
56
+ throw e
57
+ }
58
+ }
59
+
60
+ close(): Promise<void> {
61
+ this.closed = true
62
+ return this.tx?.then(async ({client}) => {
63
+ try {
64
+ await client.query('COMMIT')
65
+ } catch(e: any) {
66
+ // ignore
67
+ } finally {
68
+ client.release()
69
+ }
70
+ }) || Promise.resolve()
71
+ }
72
+ }
73
+
74
+
75
+ export function createPoolConfig(): PoolConfig {
76
+ return {
77
+ host: process.env.DB_HOST || 'localhost',
78
+ port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 5432,
79
+ database: process.env.DB_NAME || 'postgres',
80
+ user: process.env.DB_USER || 'postgres',
81
+ password: process.env.DB_PASS || 'postgres'
82
+ }
83
+ }
@@ -0,0 +1,328 @@
1
+ import {Output, toCamelCase, toPlural} from "@subsquid/util"
2
+ import assert from "assert"
3
+ import {DocumentNode, parse, print} from "graphql"
4
+ import {Entity, Enum, FTS_Query, Interface, JsonObject, Model, Prop, Union} from "../model"
5
+ import {getOrderByMapping} from "../orderBy"
6
+ import {scalars_list} from "../scalars"
7
+ import {toQueryListField} from "../util"
8
+
9
+
10
+ export function generateOpenCrudQueries(model: Model): string {
11
+ let out = new Output()
12
+
13
+ generatePageInfoType()
14
+
15
+ for (let name in model) {
16
+ let item = model[name]
17
+ switch(item.kind) {
18
+ case 'entity':
19
+ generateOrderByInput(name)
20
+ generateWhereUniqueInput(name)
21
+ generateWhereInput(name, item)
22
+ generateObjectType(name, item)
23
+ generateEntityConnection(name)
24
+ break
25
+ case 'object':
26
+ if (hasFilters(item)) {
27
+ generateWhereInput(name, item)
28
+ }
29
+ generateObjectType(name, item)
30
+ break
31
+ case 'interface':
32
+ generateObjectType(name, item)
33
+ break
34
+ case 'union':
35
+ generateUnionWhereInput(name, item)
36
+ generateUnionType(name, item)
37
+ break
38
+ case 'enum':
39
+ generateEnumType(name, item)
40
+ break
41
+ case 'fts':
42
+ generateFtsTypes(name, item)
43
+ break
44
+ }
45
+ }
46
+
47
+ out.block('type Query', () => {
48
+ for (let name in model) {
49
+ let item = model[name]
50
+ if (item.kind == 'entity') {
51
+ out.line(`${toCamelCase(name)}ById(id: ID!): ${name}`)
52
+ out.line(`${toCamelCase(name)}ByUniqueInput(where: ${name}WhereUniqueInput!): ${name} @deprecated(reason: "Use \`${toCamelCase(name)}ById\`")`)
53
+ out.line(`${toQueryListField(name)}${manyArguments(name)}: [${name}!]!`)
54
+ out.line(`${toQueryListField(name)}Connection${connectionArguments(name)}: ${toPlural(name)}Connection!`)
55
+ }
56
+ if (item.kind == 'fts') {
57
+ generateFtsQuery(name, item)
58
+ }
59
+ }
60
+ })
61
+
62
+ function generateObjectType(name: string, object: Entity | JsonObject | Interface): void {
63
+ let head: string
64
+ if (object.kind == 'interface') {
65
+ head = `interface ${name}`
66
+ } else {
67
+ head = `type ${name}`
68
+ if (object.interfaces?.length) {
69
+ head += ` implements ${object.interfaces.join(' & ')}`
70
+ }
71
+ }
72
+ generateDescription(object.description)
73
+ out.block(head, () => {
74
+ for (let key in object.properties) {
75
+ let prop = object.properties[key]
76
+ let gqlType = renderPropType(prop)
77
+ generateDescription(prop.description)
78
+ if (prop.type.kind == 'list-lookup') {
79
+ out.line(`${key}${manyArguments(prop.type.entity)}: ${gqlType}`)
80
+ } else {
81
+ out.line(`${key}: ${gqlType}`)
82
+ }
83
+ }
84
+ })
85
+ out.line()
86
+ }
87
+
88
+ function renderPropType(prop: Prop): string {
89
+ switch(prop.type.kind) {
90
+ case "list":
91
+ return `[${renderPropType(prop.type.item)}]${prop.nullable ? '' : '!'}`
92
+ case 'fk':
93
+ return `${prop.type.foreignEntity}${prop.nullable ? '' : '!'}`
94
+ case 'lookup':
95
+ return prop.type.entity
96
+ case 'list-lookup':
97
+ return `[${prop.type.entity}!]!`
98
+ default:
99
+ return prop.type.name + (prop.nullable ? '' : '!')
100
+ }
101
+ }
102
+
103
+ function manyArguments(entityName: string): string {
104
+ return `(where: ${entityName}WhereInput orderBy: [${entityName}OrderByInput] offset: Int limit: Int)`
105
+ }
106
+
107
+ function connectionArguments(entityName: string): string {
108
+ return `(orderBy: [${entityName}OrderByInput!]! after: String first: Int where: ${entityName}WhereInput)`
109
+ }
110
+
111
+ function generateOrderByInput(entityName: string): void {
112
+ out.block(`enum ${entityName}OrderByInput`, () => {
113
+ let mapping = getOrderByMapping(model, entityName)
114
+ for (let key of mapping.keys()) {
115
+ out.line(key)
116
+ }
117
+ })
118
+ out.line()
119
+ }
120
+
121
+ function generateWhereUniqueInput(entityName: string): void {
122
+ out.block(`input ${entityName}WhereUniqueInput`, () => {
123
+ out.line('id: ID!')
124
+ })
125
+ }
126
+
127
+ function generateWhereInput(name: string, object: Entity | JsonObject): void {
128
+ out.block(`input ${name}WhereInput`, () => {
129
+ generatePropsFilters(object.properties)
130
+ if (object.kind == 'entity') {
131
+ out.line(`AND: [${name}WhereInput!]`)
132
+ out.line(`OR: [${name}WhereInput!]`)
133
+ }
134
+ })
135
+ out.line()
136
+ }
137
+
138
+ function generatePropsFilters(props: Record<string, Prop>): void {
139
+ for (let key in props) {
140
+ let prop = props[key]
141
+ switch(prop.type.kind) {
142
+ case 'scalar':
143
+ case 'enum':
144
+ generateScalarFilters(key, prop.type.name)
145
+ break
146
+ case 'list':
147
+ if (prop.type.item.type.kind == 'scalar' || prop.type.item.type.kind == 'enum') {
148
+ let item = prop.type.item.type.name
149
+ out.line(`${key}_containsAll: [${item}!]`)
150
+ out.line(`${key}_containsAny: [${item}!]`)
151
+ out.line(`${key}_containsNone: [${item}!]`)
152
+ }
153
+ break
154
+ case 'object':
155
+ if (hasFilters(getObject(prop.type.name))) {
156
+ out.line(`${key}: ${prop.type.name}WhereInput`)
157
+ }
158
+ break
159
+ case 'union':
160
+ out.line(`${key}: ${prop.type.name}WhereInput`)
161
+ break
162
+ case 'fk':
163
+ out.line(`${key}: ${prop.type.foreignEntity}WhereInput`)
164
+ break
165
+ case 'lookup':
166
+ out.line(`${key}: ${prop.type.entity}WhereInput`)
167
+ break
168
+ case 'list-lookup':
169
+ out.line(`${key}_every: ${prop.type.entity}WhereInput`)
170
+ out.line(`${key}_some: ${prop.type.entity}WhereInput`)
171
+ out.line(`${key}_none: ${prop.type.entity}WhereInput`)
172
+ break
173
+ }
174
+ }
175
+ }
176
+
177
+ function hasFilters(obj: JsonObject): boolean {
178
+ for (let key in obj.properties) {
179
+ let propType = obj.properties[key].type
180
+ switch(propType.kind) {
181
+ case 'scalar':
182
+ case 'enum':
183
+ case 'union':
184
+ return true
185
+ case 'object':
186
+ if (hasFilters(getObject(propType.name))) {
187
+ return true
188
+ }
189
+ }
190
+ }
191
+ return false
192
+ }
193
+
194
+ function getObject(name: string): JsonObject {
195
+ let obj = model[name]
196
+ assert(obj.kind == 'object')
197
+ return obj
198
+ }
199
+
200
+ function generateUnionWhereInput(name: string, union: Union): void {
201
+ out.block(`input ${name}WhereInput`, () => {
202
+ // TODO: unify and use enum
203
+ out.line('isTypeOf_eq: String')
204
+ out.line('isTypeOf_not_eq: String')
205
+ out.line('isTypeOf_in: [String!]')
206
+ out.line('isTypeOf_not_in: [String!]')
207
+
208
+ let props: Record<string, Prop> = {}
209
+ union.variants.forEach(variant => {
210
+ let obj = getObject(variant)
211
+ Object.assign(props, obj.properties)
212
+ })
213
+
214
+ generatePropsFilters(props)
215
+ })
216
+ }
217
+
218
+ function generateScalarFilters(fieldName: string, graphqlType: string): void {
219
+ out.line(`${fieldName}_eq: ${graphqlType}`)
220
+ out.line(`${fieldName}_not_eq: ${graphqlType}`)
221
+
222
+ switch(graphqlType) {
223
+ case 'ID':
224
+ case 'String':
225
+ case 'Int':
226
+ case 'Float':
227
+ case 'DateTime':
228
+ case 'BigInt':
229
+ out.line(`${fieldName}_gt: ${graphqlType}`)
230
+ out.line(`${fieldName}_gte: ${graphqlType}`)
231
+ out.line(`${fieldName}_lt: ${graphqlType}`)
232
+ out.line(`${fieldName}_lte: ${graphqlType}`)
233
+ out.line(`${fieldName}_in: [${graphqlType}!]`)
234
+ out.line(`${fieldName}_not_in: [${graphqlType}!]`)
235
+ break
236
+ }
237
+
238
+ if (graphqlType == 'String' || graphqlType == 'ID') {
239
+ out.line(`${fieldName}_contains: ${graphqlType}`)
240
+ out.line(`${fieldName}_not_contains: ${graphqlType}`)
241
+ out.line(`${fieldName}_startsWith: ${graphqlType}`)
242
+ out.line(`${fieldName}_not_startsWith: ${graphqlType}`)
243
+ out.line(`${fieldName}_endsWith: ${graphqlType}`)
244
+ out.line(`${fieldName}_not_endsWith: ${graphqlType}`)
245
+ }
246
+
247
+ if (model[graphqlType]?.kind == 'enum') {
248
+ out.line(`${fieldName}_in: [${graphqlType}!]`)
249
+ out.line(`${fieldName}_not_in: [${graphqlType}!]`)
250
+ }
251
+ }
252
+
253
+ function generateUnionType(name: string, union: Union) {
254
+ generateDescription(union.description)
255
+ out.line(`union ${name} = ${union.variants.join(' | ')}`)
256
+ out.line()
257
+ }
258
+
259
+ function generateEnumType(name: string, e: Enum): void {
260
+ generateDescription(e.description)
261
+ out.block(`enum ${name}`, () => {
262
+ for (let key in e.values) {
263
+ out.line(key)
264
+ }
265
+ })
266
+ }
267
+
268
+ function generatePageInfoType(): void {
269
+ out.block(`type PageInfo`, () => {
270
+ out.line('hasNextPage: Boolean!')
271
+ out.line('hasPreviousPage: Boolean!')
272
+ out.line('startCursor: String!')
273
+ out.line('endCursor: String!')
274
+ })
275
+ out.line()
276
+ }
277
+
278
+ function generateEntityConnection(name: string): void {
279
+ out.block(`type ${name}Edge`, () => {
280
+ out.line(`node: ${name}!`)
281
+ out.line(`cursor: String!`)
282
+ })
283
+ out.line()
284
+ out.block(`type ${toPlural(name)}Connection`, () => {
285
+ out.line(`edges: [${name}Edge!]!`)
286
+ out.line(`pageInfo: PageInfo!`)
287
+ out.line(`totalCount: Int!`)
288
+ })
289
+ out.line()
290
+ }
291
+
292
+ function generateFtsTypes(name: string, query: FTS_Query): void {
293
+ let itemType = name + '_Item'
294
+ out.line(`union ${itemType} = ${query.sources.map(s => s.entity).join(' | ')}`)
295
+ out.line()
296
+ out.block(`type ${name}_Output`, () => {
297
+ out.line(`item: ${itemType}!`)
298
+ out.line(`rank: Float!`)
299
+ out.line(`highlight: String!`)
300
+ })
301
+ out.line()
302
+ }
303
+
304
+ function generateFtsQuery(name: string, query: FTS_Query): void {
305
+ let where = query.sources.map(src => {
306
+ return `where${src.entity}: ${src.entity}WhereInput`
307
+ })
308
+ out.line(`${name}(text: String! ${where.join(' ')} limit: Int offset: Int): [${name}_Output!]!`)
309
+ }
310
+
311
+ function generateDescription(description?: string): void {
312
+ if (description) {
313
+ out.line(print({
314
+ kind: 'StringValue',
315
+ value: description
316
+ }))
317
+ }
318
+ }
319
+
320
+ return out.toString()
321
+ }
322
+
323
+
324
+ export function buildServerSchema(model: Model): DocumentNode {
325
+ let scalars = scalars_list.map(name => 'scalar ' + name).join('\n')
326
+ let queries = generateOpenCrudQueries(model)
327
+ return parse(scalars + '\n\n' + queries)
328
+ }