@subsquid/openreader 5.4.0-beta.aa7384 → 5.4.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 (41) hide show
  1. package/lib/dialect/opencrud/schema.d.ts.map +1 -1
  2. package/lib/dialect/opencrud/schema.js +15 -1
  3. package/lib/dialect/opencrud/schema.js.map +1 -1
  4. package/lib/dialect/opencrud/tree.d.ts.map +1 -1
  5. package/lib/dialect/opencrud/tree.js +17 -0
  6. package/lib/dialect/opencrud/tree.js.map +1 -1
  7. package/lib/main.js +3 -1
  8. package/lib/main.js.map +1 -1
  9. package/lib/model.d.ts +1 -0
  10. package/lib/model.d.ts.map +1 -1
  11. package/lib/model.schema.d.ts.map +1 -1
  12. package/lib/model.schema.js +25 -1
  13. package/lib/model.schema.js.map +1 -1
  14. package/lib/server.d.ts +2 -1
  15. package/lib/server.d.ts.map +1 -1
  16. package/lib/server.js +4 -2
  17. package/lib/server.js.map +1 -1
  18. package/lib/sql/cursor.d.ts.map +1 -1
  19. package/lib/sql/cursor.js +8 -1
  20. package/lib/sql/cursor.js.map +1 -1
  21. package/lib/test/basic.test.js +1 -0
  22. package/lib/test/basic.test.js.map +1 -1
  23. package/lib/test/disableForeignKeyConstraint.test.d.ts +2 -0
  24. package/lib/test/disableForeignKeyConstraint.test.d.ts.map +1 -0
  25. package/lib/test/disableForeignKeyConstraint.test.js +332 -0
  26. package/lib/test/disableForeignKeyConstraint.test.js.map +1 -0
  27. package/lib/util/util.d.ts +3 -0
  28. package/lib/util/util.d.ts.map +1 -1
  29. package/lib/util/util.js +11 -0
  30. package/lib/util/util.js.map +1 -1
  31. package/package.json +5 -5
  32. package/src/dialect/opencrud/schema.ts +20 -2
  33. package/src/dialect/opencrud/tree.ts +18 -1
  34. package/src/main.ts +4 -1
  35. package/src/model.schema.ts +31 -1
  36. package/src/model.ts +1 -0
  37. package/src/server.ts +7 -3
  38. package/src/sql/cursor.ts +8 -2
  39. package/src/test/basic.test.ts +1 -0
  40. package/src/test/disableForeignKeyConstraint.test.ts +363 -0
  41. package/src/util/util.ts +16 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=disableForeignKeyConstraint.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disableForeignKeyConstraint.test.d.ts","sourceRoot":"","sources":["../../src/test/disableForeignKeyConstraint.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,332 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const expect_1 = __importDefault(require("expect"));
7
+ const graphql_1 = require("graphql");
8
+ const model_schema_1 = require("../model.schema");
9
+ const setup_1 = require("./setup");
10
+ function model(schema) {
11
+ return (0, model_schema_1.buildModel)((0, model_schema_1.buildSchema)((0, graphql_1.parse)(schema)));
12
+ }
13
+ describe('@disableForeignKeyConstraint', function () {
14
+ describe('schema validation', function () {
15
+ it('parses valid usage and sets disableConstraint flag', function () {
16
+ let m = model(`
17
+ type Account @entity {
18
+ id: ID!
19
+ }
20
+ type Transfer @entity {
21
+ id: ID!
22
+ from: Account @disableForeignKeyConstraint
23
+ }
24
+ `);
25
+ (0, expect_1.default)(m.Transfer).toBeDefined();
26
+ (0, expect_1.default)(m.Transfer.kind).toBe('entity');
27
+ if (m.Transfer.kind === 'entity') {
28
+ let fromProp = m.Transfer.properties.from;
29
+ (0, expect_1.default)(fromProp.type).toEqual({
30
+ kind: 'fk',
31
+ entity: 'Account',
32
+ disableConstraint: true,
33
+ });
34
+ }
35
+ });
36
+ it('rejects non-nullable FK field', function () {
37
+ (0, expect_1.default)(() => model(`
38
+ type Account @entity {
39
+ id: ID!
40
+ }
41
+ type Transfer @entity {
42
+ id: ID!
43
+ from: Account! @disableForeignKeyConstraint
44
+ }
45
+ `)).toThrow(model_schema_1.SchemaError);
46
+ });
47
+ it('rejects directive on scalar field', function () {
48
+ (0, expect_1.default)(() => model(`
49
+ type Account @entity {
50
+ id: ID!
51
+ name: String @disableForeignKeyConstraint
52
+ }
53
+ `)).toThrow(model_schema_1.SchemaError);
54
+ });
55
+ it('rejects directive on enum field', function () {
56
+ (0, expect_1.default)(() => model(`
57
+ enum Status { ACTIVE INACTIVE }
58
+ type Account @entity {
59
+ id: ID!
60
+ status: Status @disableForeignKeyConstraint
61
+ }
62
+ `)).toThrow(model_schema_1.SchemaError);
63
+ });
64
+ it('rejects directive on list (derivedFrom) field', function () {
65
+ (0, expect_1.default)(() => model(`
66
+ type Account @entity {
67
+ id: ID!
68
+ transfers: [Transfer!] @derivedFrom(field: "from") @disableForeignKeyConstraint
69
+ }
70
+ type Transfer @entity {
71
+ id: ID!
72
+ from: Account
73
+ }
74
+ `)).toThrow(model_schema_1.SchemaError);
75
+ });
76
+ it('rejects directive on @derivedFrom lookup field', function () {
77
+ (0, expect_1.default)(() => model(`
78
+ type Account @entity {
79
+ id: ID!
80
+ profile: Profile @derivedFrom(field: "account") @disableForeignKeyConstraint
81
+ }
82
+ type Profile @entity {
83
+ id: ID!
84
+ account: Account! @unique
85
+ }
86
+ `)).toThrow(model_schema_1.SchemaError);
87
+ });
88
+ it('rejects directive on non-entity type', function () {
89
+ (0, expect_1.default)(() => model(`
90
+ type Account @entity {
91
+ id: ID!
92
+ }
93
+ type Metadata {
94
+ ref: Account @disableForeignKeyConstraint
95
+ }
96
+ type Transfer @entity {
97
+ id: ID!
98
+ meta: Metadata
99
+ }
100
+ `)).toThrow(model_schema_1.SchemaError);
101
+ });
102
+ });
103
+ describe('runtime (opencrud)', function () {
104
+ // Database setup: transfer table has from_id and to_id columns
105
+ // with NO foreign key constraints. Some IDs reference accounts
106
+ // that don't exist, simulating @disableForeignKeyConstraint behavior.
107
+ (0, setup_1.useDatabase)([
108
+ `create table account (id text primary key, name text)`,
109
+ `create table transfer (id text primary key, from_id text, to_id text, amount numeric)`,
110
+ // account '1' exists, account '2' exists, but account 'missing' does NOT exist
111
+ `insert into account (id, name) values ('1', 'Alice')`,
112
+ `insert into account (id, name) values ('2', 'Bob')`,
113
+ `insert into transfer (id, from_id, to_id, amount) values ('t1', '1', '2', 100)`,
114
+ `insert into transfer (id, from_id, to_id, amount) values ('t2', '2', 'missing', 50)`,
115
+ `insert into transfer (id, from_id, to_id, amount) values ('t3', 'missing', '1', 75)`,
116
+ `insert into transfer (id, from_id, to_id, amount) values ('t4', null, '1', 25)`,
117
+ ]);
118
+ const client = (0, setup_1.useServer)(`
119
+ type Account @entity {
120
+ id: ID!
121
+ name: String
122
+ transfersFrom: [Transfer!] @derivedFrom(field: "from")
123
+ transfersTo: [Transfer!] @derivedFrom(field: "to")
124
+ }
125
+
126
+ type Transfer @entity {
127
+ id: ID!
128
+ from: Account @disableForeignKeyConstraint
129
+ to: Account @disableForeignKeyConstraint
130
+ amount: Int!
131
+ }
132
+ `);
133
+ it('returns null for FK relation when referenced row is missing, but exposes raw ID via {field}Id', function () {
134
+ return client.test(`
135
+ query {
136
+ transfers(orderBy: [id_ASC]) {
137
+ id
138
+ from { id name }
139
+ fromId
140
+ to { id name }
141
+ toId
142
+ amount
143
+ }
144
+ }
145
+ `, {
146
+ transfers: [
147
+ { id: 't1', from: { id: '1', name: 'Alice' }, fromId: '1', to: { id: '2', name: 'Bob' }, toId: '2', amount: 100 },
148
+ { id: 't2', from: { id: '2', name: 'Bob' }, fromId: '2', to: null, toId: 'missing', amount: 50 },
149
+ { id: 't3', from: null, fromId: 'missing', to: { id: '1', name: 'Alice' }, toId: '1', amount: 75 },
150
+ { id: 't4', from: null, fromId: null, to: { id: '1', name: 'Alice' }, toId: '1', amount: 25 },
151
+ ]
152
+ });
153
+ });
154
+ it('can query only {field}Id without the relation', function () {
155
+ return client.test(`
156
+ query {
157
+ transfers(orderBy: [id_ASC]) {
158
+ id
159
+ fromId
160
+ toId
161
+ }
162
+ }
163
+ `, {
164
+ transfers: [
165
+ { id: 't1', fromId: '1', toId: '2' },
166
+ { id: 't2', fromId: '2', toId: 'missing' },
167
+ { id: 't3', fromId: 'missing', toId: '1' },
168
+ { id: 't4', fromId: null, toId: '1' },
169
+ ]
170
+ });
171
+ });
172
+ it('supports {field}Id_eq filter', function () {
173
+ return client.test(`
174
+ query {
175
+ transfers(where: {fromId_eq: "missing"}, orderBy: [id_ASC]) {
176
+ id
177
+ fromId
178
+ }
179
+ }
180
+ `, {
181
+ transfers: [
182
+ { id: 't3', fromId: 'missing' },
183
+ ]
184
+ });
185
+ });
186
+ it('supports {field}Id_not_eq filter', function () {
187
+ return client.test(`
188
+ query {
189
+ transfers(where: {fromId_not_eq: "missing"}, orderBy: [id_ASC]) {
190
+ id
191
+ fromId
192
+ }
193
+ }
194
+ `, {
195
+ transfers: [
196
+ { id: 't1', fromId: '1' },
197
+ { id: 't2', fromId: '2' },
198
+ ]
199
+ });
200
+ });
201
+ it('supports {field}Id_in filter', function () {
202
+ return client.test(`
203
+ query {
204
+ transfers(where: {fromId_in: ["1", "missing"]}, orderBy: [id_ASC]) {
205
+ id
206
+ fromId
207
+ }
208
+ }
209
+ `, {
210
+ transfers: [
211
+ { id: 't1', fromId: '1' },
212
+ { id: 't3', fromId: 'missing' },
213
+ ]
214
+ });
215
+ });
216
+ it('supports {field}Id_not_in filter', function () {
217
+ return client.test(`
218
+ query {
219
+ transfers(where: {fromId_not_in: ["1", "missing"]}, orderBy: [id_ASC]) {
220
+ id
221
+ fromId
222
+ }
223
+ }
224
+ `, {
225
+ transfers: [
226
+ { id: 't2', fromId: '2' },
227
+ ]
228
+ });
229
+ });
230
+ it('supports {field}Id_isNull filter', function () {
231
+ return client.test(`
232
+ query {
233
+ transfers(where: {fromId_isNull: true}, orderBy: [id_ASC]) {
234
+ id
235
+ fromId
236
+ }
237
+ }
238
+ `, {
239
+ transfers: [
240
+ { id: 't4', fromId: null },
241
+ ]
242
+ });
243
+ });
244
+ it('supports {field}Id_contains filter', function () {
245
+ return client.test(`
246
+ query {
247
+ transfers(where: {fromId_contains: "iss"}, orderBy: [id_ASC]) {
248
+ id
249
+ fromId
250
+ }
251
+ }
252
+ `, {
253
+ transfers: [
254
+ { id: 't3', fromId: 'missing' },
255
+ ]
256
+ });
257
+ });
258
+ it('supports {field}Id_startsWith filter', function () {
259
+ return client.test(`
260
+ query {
261
+ transfers(where: {fromId_startsWith: "mis"}, orderBy: [id_ASC]) {
262
+ id
263
+ fromId
264
+ }
265
+ }
266
+ `, {
267
+ transfers: [
268
+ { id: 't3', fromId: 'missing' },
269
+ ]
270
+ });
271
+ });
272
+ it('supports {field}Id_endsWith filter', function () {
273
+ return client.test(`
274
+ query {
275
+ transfers(where: {toId_endsWith: "2"}, orderBy: [id_ASC]) {
276
+ id
277
+ toId
278
+ }
279
+ }
280
+ `, {
281
+ transfers: [
282
+ { id: 't1', toId: '2' },
283
+ ]
284
+ });
285
+ });
286
+ it('supports {field}Id_gt and {field}Id_lt filters', function () {
287
+ return client.test(`
288
+ query {
289
+ transfers(where: {fromId_gt: "1", fromId_lt: "missing"}, orderBy: [id_ASC]) {
290
+ id
291
+ fromId
292
+ }
293
+ }
294
+ `, {
295
+ transfers: [
296
+ { id: 't2', fromId: '2' },
297
+ ]
298
+ });
299
+ });
300
+ it('can combine {field}Id filter with other filters', function () {
301
+ return client.test(`
302
+ query {
303
+ transfers(where: {fromId_eq: "2", amount_gt: 40}, orderBy: [id_ASC]) {
304
+ id
305
+ fromId
306
+ amount
307
+ }
308
+ }
309
+ `, {
310
+ transfers: [
311
+ { id: 't2', fromId: '2', amount: 50 },
312
+ ]
313
+ });
314
+ });
315
+ it('can combine {field}Id filter with relation filter in OR', function () {
316
+ return client.test(`
317
+ query {
318
+ transfers(where: {OR: [{fromId_eq: "missing"}, {from: {name_eq: "Alice"}}]}, orderBy: [id_ASC]) {
319
+ id
320
+ fromId
321
+ }
322
+ }
323
+ `, {
324
+ transfers: [
325
+ { id: 't1', fromId: '1' },
326
+ { id: 't3', fromId: 'missing' },
327
+ ]
328
+ });
329
+ });
330
+ });
331
+ });
332
+ //# sourceMappingURL=disableForeignKeyConstraint.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disableForeignKeyConstraint.test.js","sourceRoot":"","sources":["../../src/test/disableForeignKeyConstraint.test.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { Prop } from "../model";
1
2
  export declare function toColumn(gqlFieldName: string): string;
2
3
  export declare function toFkColumn(gqlFieldName: string): string;
3
4
  export declare function toTable(entityName: string): string;
@@ -5,4 +6,6 @@ export declare function ensureArray<T>(item: T | T[]): T[];
5
6
  export declare function toSafeInteger(s: number | string): number;
6
7
  export declare function invalidFormat(type: string, value: string): Error;
7
8
  export declare function identity<T>(x: T): T;
9
+ export declare function toFkIdField(fkFieldName: string): string;
10
+ export declare function getFkPropByIdField(idFieldName: string, properties: Record<string, Prop>): Prop | undefined;
8
11
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/util/util.ts"],"names":[],"mappings":"AAIA,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,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAIxD;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAEhE;AAGD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAEnC"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/util/util.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,IAAI,EAAC,MAAM,UAAU,CAAA;AAG7B,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,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAIxD;AAGD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAEhE;AAGD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAEnC;AAGD,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEvD;AAGD,wBAAgB,kBAAkB,CAC9B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GACjC,IAAI,GAAG,SAAS,CAIlB"}
package/lib/util/util.js CHANGED
@@ -10,6 +10,8 @@ exports.ensureArray = ensureArray;
10
10
  exports.toSafeInteger = toSafeInteger;
11
11
  exports.invalidFormat = invalidFormat;
12
12
  exports.identity = identity;
13
+ exports.toFkIdField = toFkIdField;
14
+ exports.getFkPropByIdField = getFkPropByIdField;
13
15
  const util_naming_1 = require("@subsquid/util-naming");
14
16
  const assert_1 = __importDefault(require("assert"));
15
17
  function toColumn(gqlFieldName) {
@@ -35,4 +37,13 @@ function invalidFormat(type, value) {
35
37
  function identity(x) {
36
38
  return x;
37
39
  }
40
+ function toFkIdField(fkFieldName) {
41
+ return fkFieldName + 'Id';
42
+ }
43
+ function getFkPropByIdField(idFieldName, properties) {
44
+ if (!idFieldName.endsWith('Id'))
45
+ return undefined;
46
+ let fkProp = properties[idFieldName.slice(0, -2)];
47
+ return fkProp?.type.kind == 'fk' ? fkProp : undefined;
48
+ }
38
49
  //# sourceMappingURL=util.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/util/util.ts"],"names":[],"mappings":";;;;;AAIA,4BAEC;AAGD,gCAEC;AAGD,0BAEC;AAGD,kCAEC;AAGD,sCAIC;AAGD,sCAEC;AAGD,4BAEC;AAtCD,uDAAiD;AACjD,oDAA2B;AAG3B,SAAgB,QAAQ,CAAC,YAAoB;IACzC,OAAO,IAAA,yBAAW,EAAC,YAAY,CAAC,CAAA;AACpC,CAAC;AAGD,SAAgB,UAAU,CAAC,YAAoB;IAC3C,OAAO,IAAA,yBAAW,EAAC,YAAY,CAAC,GAAG,KAAK,CAAA;AAC5C,CAAC;AAGD,SAAgB,OAAO,CAAC,UAAkB;IACtC,OAAO,IAAA,yBAAW,EAAC,UAAU,CAAC,CAAA;AAClC,CAAC;AAGD,SAAgB,WAAW,CAAI,IAAa;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;AAC9C,CAAC;AAGD,SAAgB,aAAa,CAAC,CAAkB;IAC5C,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAW,EAAE,EAAE,CAAC,CAAA;IACjC,IAAA,gBAAM,EAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/B,OAAO,CAAC,CAAA;AACZ,CAAC;AAGD,SAAgB,aAAa,CAAC,IAAY,EAAE,KAAa;IACrD,OAAO,IAAI,SAAS,CAAC,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,CAAA;AACnD,CAAC;AAGD,SAAgB,QAAQ,CAAI,CAAI;IAC5B,OAAO,CAAC,CAAA;AACZ,CAAC"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/util/util.ts"],"names":[],"mappings":";;;;;AAKA,4BAEC;AAGD,gCAEC;AAGD,0BAEC;AAGD,kCAEC;AAGD,sCAIC;AAGD,sCAEC;AAGD,4BAEC;AAGD,kCAEC;AAGD,gDAOC;AAtDD,uDAAiD;AACjD,oDAA2B;AAI3B,SAAgB,QAAQ,CAAC,YAAoB;IACzC,OAAO,IAAA,yBAAW,EAAC,YAAY,CAAC,CAAA;AACpC,CAAC;AAGD,SAAgB,UAAU,CAAC,YAAoB;IAC3C,OAAO,IAAA,yBAAW,EAAC,YAAY,CAAC,GAAG,KAAK,CAAA;AAC5C,CAAC;AAGD,SAAgB,OAAO,CAAC,UAAkB;IACtC,OAAO,IAAA,yBAAW,EAAC,UAAU,CAAC,CAAA;AAClC,CAAC;AAGD,SAAgB,WAAW,CAAI,IAAa;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;AAC9C,CAAC;AAGD,SAAgB,aAAa,CAAC,CAAkB;IAC5C,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAW,EAAE,EAAE,CAAC,CAAA;IACjC,IAAA,gBAAM,EAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/B,OAAO,CAAC,CAAA;AACZ,CAAC;AAGD,SAAgB,aAAa,CAAC,IAAY,EAAE,KAAa;IACrD,OAAO,IAAI,SAAS,CAAC,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,CAAA;AACnD,CAAC;AAGD,SAAgB,QAAQ,CAAI,CAAI;IAC5B,OAAO,CAAC,CAAA;AACZ,CAAC;AAGD,SAAgB,WAAW,CAAC,WAAmB;IAC3C,OAAO,WAAW,GAAG,IAAI,CAAA;AAC7B,CAAC;AAGD,SAAgB,kBAAkB,CAC9B,WAAmB,EACnB,UAAgC;IAEhC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IACjD,IAAI,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IACjD,OAAO,MAAM,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;AACzD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subsquid/openreader",
3
- "version": "5.4.0-beta.aa7384",
3
+ "version": "5.4.0",
4
4
  "description": "GraphQL server for postgres-compatible databases",
5
5
  "keywords": [
6
6
  "graphql",
@@ -8,7 +8,7 @@
8
8
  "postgres"
9
9
  ],
10
10
  "license": "GPL-3.0-or-later",
11
- "repository": "git@github.com:subsquid/squid.git",
11
+ "repository": "git@github.com:subsquid/squid-sdk.git",
12
12
  "publishConfig": {
13
13
  "access": "public"
14
14
  },
@@ -23,14 +23,14 @@
23
23
  "dependencies": {
24
24
  "@graphql-tools/merge": "^9.0.1",
25
25
  "@subsquid/graphiql-console": "^0.3.0",
26
- "@subsquid/logger": "^1.3.3",
26
+ "@subsquid/logger": "^1.4.0",
27
27
  "@subsquid/util-internal": "^3.2.0",
28
28
  "@subsquid/util-internal-commander": "^1.4.0",
29
29
  "@subsquid/util-internal-hex": "^1.2.2",
30
- "@subsquid/util-internal-http-server": "^2.0.0",
30
+ "@subsquid/util-internal-http-server": "^2.0.1",
31
31
  "@subsquid/util-naming": "^1.3.0",
32
32
  "@subsquid/apollo-server-core": "^3.14.0",
33
- "@subsquid/apollo-server-express": "^3.14.0",
33
+ "@subsquid/apollo-server-express": "^3.14.1",
34
34
  "commander": "^11.1.0",
35
35
  "deep-equal": "^2.2.3",
36
36
  "express": "^4.18.2",
@@ -37,7 +37,7 @@ import {customScalars} from '../../scalars'
37
37
  import {ConnectionQuery, CountQuery, EntityByIdQuery, ListQuery, Query} from '../../sql/query'
38
38
  import {Limit} from '../../util/limit'
39
39
  import {getResolveTree, getTreeRequest, hasTreeRequest, simplifyResolveTree} from '../../util/resolve-tree'
40
- import {ensureArray, identity} from '../../util/util'
40
+ import {ensureArray, identity, toFkIdField} from '../../util/util'
41
41
  import {getOrderByMapping, parseOrderBy} from './orderBy'
42
42
  import {parseAnyTree, parseObjectTree, parseSqlArguments} from './tree'
43
43
  import {parseWhere} from './where'
@@ -152,6 +152,15 @@ export class SchemaBuilder {
152
152
  field.resolve = (source, args, context, info) => source[info.path.key]
153
153
  break
154
154
  }
155
+ if (prop.type.kind == 'fk') {
156
+ let idKey = toFkIdField(key)
157
+ if (!object.properties[idKey]) {
158
+ fields[idKey] = {
159
+ description: prop.description,
160
+ type: this.getPropType({type: {kind: 'scalar', name: 'String'}, nullable: prop.nullable}),
161
+ }
162
+ }
163
+ }
155
164
  }
156
165
  fields[key] = field
157
166
  }
@@ -303,7 +312,16 @@ export class SchemaBuilder {
303
312
  fields[`${key}_isNull`] = {type: GraphQLBoolean}
304
313
  fields[key] = {type: this.getWhere(prop.type.name)}
305
314
  break
306
- case "fk":
315
+ case "fk": {
316
+ fields[`${key}_isNull`] = {type: GraphQLBoolean}
317
+ fields[key] = {type: this.getWhere(prop.type.entity)}
318
+ this.buildPropWhereFilters(
319
+ toFkIdField(key),
320
+ {type: {kind: 'scalar', name: 'String'}, nullable: prop.nullable},
321
+ fields
322
+ )
323
+ break
324
+ }
307
325
  case "lookup":
308
326
  fields[`${key}_isNull`] = {type: GraphQLBoolean}
309
327
  fields[key] = {type: this.getWhere(prop.type.entity)}
@@ -7,7 +7,7 @@ import {AnyFields, FieldRequest, FieldsByEntity, OpaqueRequest} from '../../ir/f
7
7
  import {Model} from '../../model'
8
8
  import {getQueryableEntities} from '../../model.tools'
9
9
  import {simplifyResolveTree} from '../../util/resolve-tree'
10
- import {ensureArray} from '../../util/util'
10
+ import {ensureArray, getFkPropByIdField} from '../../util/util'
11
11
  import {parseOrderBy} from './orderBy'
12
12
  import {parseWhere} from './where'
13
13
 
@@ -27,6 +27,23 @@ export function parseObjectTree(
27
27
  for (let alias in fields) {
28
28
  let f = fields[alias]
29
29
  let prop = object.properties[f.name]
30
+ if (!prop) {
31
+ let fkProp = getFkPropByIdField(f.name, object.properties)
32
+ if (fkProp) {
33
+ if (requestedScalars[f.name] == null) {
34
+ requestedScalars[f.name] = true
35
+ requests.push({
36
+ field: f.name,
37
+ aliases: [f.name],
38
+ kind: 'scalar',
39
+ type: {kind: 'scalar', name: 'String'},
40
+ prop: {type: {kind: 'scalar', name: 'String'}, nullable: fkProp.nullable},
41
+ index: 0
42
+ } as OpaqueRequest)
43
+ }
44
+ continue
45
+ }
46
+ }
30
47
  switch(prop.type.kind) {
31
48
  case "scalar":
32
49
  case "enum":
package/src/main.ts CHANGED
@@ -29,6 +29,7 @@ GraphQL server for postgres-compatible databases
29
29
  program.option('--max-root-fields <count>', 'max number of root fields in a query', nat)
30
30
  program.option('--max-response-size <nodes>', 'max response size measured in nodes', nat)
31
31
  program.option('--sql-statement-timeout <ms>', 'sql statement timeout in ms', nat)
32
+ program.option('--validation-max-errors <count>', 'max validation errors', nat)
32
33
  program.option('--subscriptions', 'enable gql subscriptions')
33
34
  program.option('--subscription-poll-interval <ms>', 'subscription poll interval in ms', nat, 1000)
34
35
  program.option('--subscription-max-response-size <nodes>', 'max response size measured in nodes', nat)
@@ -42,6 +43,7 @@ GraphQL server for postgres-compatible databases
42
43
  maxRootFields?: number
43
44
  maxResponseSize?: number
44
45
  sqlStatementTimeout?: number
46
+ validationMaxErrors?: number
45
47
  subscriptions?: boolean
46
48
  subscriptionPollInterval: number
47
49
  subscriptionMaxResponseSize?: number
@@ -65,7 +67,8 @@ GraphQL server for postgres-compatible databases
65
67
  maxResponseNodes: opts.maxResponseSize,
66
68
  subscriptions: opts.subscriptions,
67
69
  subscriptionPollInterval: opts.subscriptionPollInterval,
68
- subscriptionMaxResponseNodes: opts.subscriptionMaxResponseSize
70
+ subscriptionMaxResponseNodes: opts.subscriptionMaxResponseSize,
71
+ validationMaxErrors: opts.validationMaxErrors
69
72
  })
70
73
 
71
74
  LOG.info(`listening on port ${server.port}`)
@@ -32,6 +32,7 @@ const baseSchema = buildASTSchema(parse(`
32
32
  directive @fulltext(query: String!) on FIELD_DEFINITION
33
33
  directive @cardinality(value: Int!) on OBJECT | FIELD_DEFINITION
34
34
  directive @byteWeight(value: Float!) on FIELD_DEFINITION
35
+ directive @disableForeignKeyConstraint on FIELD_DEFINITION
35
36
  directive @variant on OBJECT # legacy
36
37
  directive @jsonField on OBJECT # legacy
37
38
  scalar ID
@@ -132,6 +133,7 @@ function addEntityOrJsonObjectOrInterface(model: Model, type: GraphQLObjectType
132
133
  let derivedFrom = checkDerivedFrom(type, f)
133
134
  let index = checkFieldIndex(type, f)
134
135
  let unique = index?.unique || false
136
+ let fkConstraint = checkDisableForeignKeyConstraint(type, f)
135
137
  let limits = {
136
138
  ...checkByteWeightDirective(type, f),
137
139
  ...checkCardinalityLimitDirective(type, f)
@@ -199,10 +201,14 @@ function addEntityOrJsonObjectOrInterface(model: Model, type: GraphQLObjectType
199
201
  description
200
202
  }
201
203
  } else {
204
+ if (fkConstraint.disableConstraint && !nullable) {
205
+ throw new SchemaError(`Property ${propName} must be nullable when @disableForeignKeyConstraint is applied`)
206
+ }
202
207
  properties[key] = {
203
208
  type: {
204
209
  kind: 'fk',
205
- entity: fieldType.name
210
+ entity: fieldType.name,
211
+ ...fkConstraint
206
212
  },
207
213
  nullable,
208
214
  unique,
@@ -509,6 +515,30 @@ function checkCardinalityLimitDirective(type: GraphQLNamedType, f: GraphQLField<
509
515
  }
510
516
 
511
517
 
518
+ function checkDisableForeignKeyConstraint(type: GraphQLNamedType, f: GraphQLField<any, any>): {disableConstraint?: boolean} {
519
+ let directives = f.astNode?.directives?.filter(d => d.name.value == 'disableForeignKeyConstraint') || []
520
+ if (directives.length == 0) return {}
521
+ if (!isEntityType(type)) throw new SchemaError(
522
+ `@disableForeignKeyConstraint was applied to ${type.name}.${f.name}, but only entity fields can have this directive`
523
+ )
524
+ if (directives.length > 1) throw new SchemaError(
525
+ `Multiple @disableForeignKeyConstraint directives were applied to ${type.name}.${f.name}`
526
+ )
527
+ let fieldType = asNonNull(f)
528
+ let list = unwrapList(fieldType)
529
+ if (list.nulls.length > 0) throw new SchemaError(
530
+ `@disableForeignKeyConstraint was applied to ${type.name}.${f.name}, but list fields cannot have this directive`
531
+ )
532
+ if (!isEntityType(list.item)) throw new SchemaError(
533
+ `@disableForeignKeyConstraint was applied to ${type.name}.${f.name}, but only foreign key fields can have this directive`
534
+ )
535
+ if (f.astNode?.directives?.some(d => d.name.value == 'derivedFrom')) throw new SchemaError(
536
+ `@disableForeignKeyConstraint was applied to ${type.name}.${f.name}, but @derivedFrom fields cannot have this directive`
537
+ )
538
+ return {disableConstraint: true}
539
+ }
540
+
541
+
512
542
  function checkByteWeightDirective(type: GraphQLNamedType, f: GraphQLField<any, any>): {byteWeight?: number} {
513
543
  let directives = f.astNode?.directives?.filter(d => d.name.value == 'byteWeight') || []
514
544
  if (directives.length > 1) throw new SchemaError(
package/src/model.ts CHANGED
@@ -112,6 +112,7 @@ export interface ListPropType {
112
112
  export interface FkPropType {
113
113
  kind: 'fk'
114
114
  entity: Name
115
+ disableConstraint?: boolean
115
116
  }
116
117
 
117
118