@nxgt/graphql-validation 0.5.0 → 0.7.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.
- package/README.md +73 -6
- package/dist/bad-user-input.d.ts +17 -2
- package/dist/bad-user-input.d.ts.map +1 -1
- package/dist/builder/check-constraints.d.ts +4 -2
- package/dist/builder/check-constraints.d.ts.map +1 -1
- package/dist/builder/declared.d.ts +12 -0
- package/dist/builder/declared.d.ts.map +1 -0
- package/dist/chunks/{index-xcaf9kes.js → index-atwkcfg3.js} +5 -5
- package/dist/chunks/{index-xcaf9kes.js.map → index-atwkcfg3.js.map} +2 -2
- package/dist/chunks/{index-twevz7yc.js → index-w9pw7fsk.js} +68 -34
- package/dist/chunks/{index-twevz7yc.js.map → index-w9pw7fsk.js.map} +6 -5
- package/dist/cli.js +1 -1
- package/dist/codegen.js +2 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +497 -10
- package/dist/index.js.map +13 -5
- package/dist/limits/cost-context.d.ts +26 -0
- package/dist/limits/cost-context.d.ts.map +1 -0
- package/dist/limits/describe.d.ts +9 -0
- package/dist/limits/describe.d.ts.map +1 -0
- package/dist/limits/introspection.d.ts +12 -0
- package/dist/limits/introspection.d.ts.map +1 -0
- package/dist/limits/list-size.d.ts +23 -0
- package/dist/limits/list-size.d.ts.map +1 -0
- package/dist/limits/max-depth.d.ts +28 -0
- package/dist/limits/max-depth.d.ts.map +1 -0
- package/dist/limits/max-fields.d.ts +35 -0
- package/dist/limits/max-fields.d.ts.map +1 -0
- package/dist/limits/max-list-cost.d.ts +77 -0
- package/dist/limits/max-list-cost.d.ts.map +1 -0
- package/dist/limits/pending-size.d.ts +13 -0
- package/dist/limits/pending-size.d.ts.map +1 -0
- package/dist/with-validation.d.ts.map +1 -1
- package/docs/README.md +1 -0
- package/docs/guide/constraints.md +3 -3
- package/docs/guide/errors.md +12 -3
- package/docs/guide/query-limits.md +311 -0
- package/docs/roadmap.md +9 -0
- package/docs/troubleshooting.md +73 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
constraintOf,
|
|
3
3
|
constraintTypeDefs2
|
|
4
|
-
} from "./chunks/index-
|
|
4
|
+
} from "./chunks/index-atwkcfg3.js";
|
|
5
5
|
import {
|
|
6
6
|
checkConstraints2
|
|
7
|
-
} from "./chunks/index-
|
|
7
|
+
} from "./chunks/index-w9pw7fsk.js";
|
|
8
8
|
|
|
9
9
|
// src/bad-user-input.ts
|
|
10
10
|
import { GraphQLError } from "graphql";
|
|
@@ -12,7 +12,13 @@ function issuesOf(error, constraintOf) {
|
|
|
12
12
|
return error.issues.map((issue) => {
|
|
13
13
|
const path = issue.path.map((key) => typeof key === "number" ? key : String(key));
|
|
14
14
|
const constraint = constraintOf?.(issue);
|
|
15
|
-
return constraint ? {
|
|
15
|
+
return constraint ? {
|
|
16
|
+
path,
|
|
17
|
+
message: issue.message,
|
|
18
|
+
code: issue.code,
|
|
19
|
+
constraint: constraint.argument,
|
|
20
|
+
value: constraint.value
|
|
21
|
+
} : { path, message: issue.message, code: issue.code };
|
|
16
22
|
});
|
|
17
23
|
}
|
|
18
24
|
function errorOf(where, issues) {
|
|
@@ -125,6 +131,477 @@ function codeOf(issue) {
|
|
|
125
131
|
return FIELD_CODES.invalid;
|
|
126
132
|
}
|
|
127
133
|
}
|
|
134
|
+
// src/limits/max-depth.ts
|
|
135
|
+
import {
|
|
136
|
+
GraphQLError as GraphQLError3
|
|
137
|
+
} from "graphql";
|
|
138
|
+
|
|
139
|
+
// src/limits/describe.ts
|
|
140
|
+
function operationLabel(operation) {
|
|
141
|
+
return operation.name ? `Operation "${operation.name.value}"` : "Operation";
|
|
142
|
+
}
|
|
143
|
+
function assertInteger(option, value, min, max = Number.MAX_SAFE_INTEGER) {
|
|
144
|
+
if (!Number.isInteger(value) || value < min || value >= max) {
|
|
145
|
+
throw new RangeError(`${option} must be an integer from ${min} to ${max - 1}.`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/limits/introspection.ts
|
|
150
|
+
import {
|
|
151
|
+
isEnumType,
|
|
152
|
+
isInputObjectType,
|
|
153
|
+
isInterfaceType,
|
|
154
|
+
isObjectType,
|
|
155
|
+
isUnionType,
|
|
156
|
+
SchemaMetaFieldDef,
|
|
157
|
+
TypeMetaFieldDef
|
|
158
|
+
} from "graphql";
|
|
159
|
+
function isIntrospectionRoot(name) {
|
|
160
|
+
return name === "__schema" || name === "__type";
|
|
161
|
+
}
|
|
162
|
+
function introspectionRoot(name) {
|
|
163
|
+
if (name === "__schema")
|
|
164
|
+
return SchemaMetaFieldDef;
|
|
165
|
+
if (name === "__type")
|
|
166
|
+
return TypeMetaFieldDef;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
var largest = new WeakMap;
|
|
170
|
+
var typeCounts = new WeakMap;
|
|
171
|
+
function typeCount(schema) {
|
|
172
|
+
let count = typeCounts.get(schema);
|
|
173
|
+
if (count === undefined) {
|
|
174
|
+
count = Object.keys(schema.getTypeMap()).length;
|
|
175
|
+
typeCounts.set(schema, count);
|
|
176
|
+
}
|
|
177
|
+
return count;
|
|
178
|
+
}
|
|
179
|
+
function largestCount(schema) {
|
|
180
|
+
const known = largest.get(schema);
|
|
181
|
+
if (known !== undefined)
|
|
182
|
+
return known;
|
|
183
|
+
let max = schema.getDirectives().length;
|
|
184
|
+
for (const directive of schema.getDirectives()) {
|
|
185
|
+
max = Math.max(max, directive.args.length, directive.locations.length);
|
|
186
|
+
}
|
|
187
|
+
for (const type of Object.values(schema.getTypeMap())) {
|
|
188
|
+
max = Math.max(max, countsOf(schema, type));
|
|
189
|
+
}
|
|
190
|
+
largest.set(schema, max);
|
|
191
|
+
return max;
|
|
192
|
+
}
|
|
193
|
+
function countsOf(schema, type) {
|
|
194
|
+
if (isObjectType(type) || isInterfaceType(type)) {
|
|
195
|
+
const fields = Object.values(type.getFields());
|
|
196
|
+
return Math.max(fields.length, type.getInterfaces().length, isInterfaceType(type) ? schema.getPossibleTypes(type).length : 0, ...fields.map((field) => field.args.length));
|
|
197
|
+
}
|
|
198
|
+
if (isInputObjectType(type))
|
|
199
|
+
return Object.keys(type.getFields()).length;
|
|
200
|
+
if (isEnumType(type))
|
|
201
|
+
return type.getValues().length;
|
|
202
|
+
if (isUnionType(type))
|
|
203
|
+
return type.getTypes().length;
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
function introspectionSize(schema, field) {
|
|
207
|
+
return field === "types" ? typeCount(schema) : largestCount(schema);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/limits/max-depth.ts
|
|
211
|
+
function depthOf(depth, selectionSet) {
|
|
212
|
+
let deepest = 0;
|
|
213
|
+
for (const selection of selectionSet.selections) {
|
|
214
|
+
let here = 0;
|
|
215
|
+
if (selection.kind === "Field") {
|
|
216
|
+
const name = selection.name.value;
|
|
217
|
+
if (name === "__typename")
|
|
218
|
+
continue;
|
|
219
|
+
if (depth.ignoreIntrospection && isIntrospectionRoot(name))
|
|
220
|
+
continue;
|
|
221
|
+
here = 1 + (selection.selectionSet ? depthOf(depth, selection.selectionSet) : 0);
|
|
222
|
+
} else if (selection.kind === "InlineFragment") {
|
|
223
|
+
here = depthOf(depth, selection.selectionSet);
|
|
224
|
+
} else {
|
|
225
|
+
here = fragmentDepth(depth, selection.name.value);
|
|
226
|
+
}
|
|
227
|
+
if (here > deepest)
|
|
228
|
+
deepest = here;
|
|
229
|
+
}
|
|
230
|
+
return deepest;
|
|
231
|
+
}
|
|
232
|
+
function fragmentDepth(depth, name) {
|
|
233
|
+
const known = depth.fragmentDepths.get(name);
|
|
234
|
+
if (known !== undefined)
|
|
235
|
+
return known;
|
|
236
|
+
const fragment = depth.getFragment(name);
|
|
237
|
+
if (!fragment || depth.measuring.has(name))
|
|
238
|
+
return 0;
|
|
239
|
+
depth.measuring.add(name);
|
|
240
|
+
const result = depthOf(depth, fragment.selectionSet);
|
|
241
|
+
depth.measuring.delete(name);
|
|
242
|
+
depth.fragmentDepths.set(name, result);
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
function maxDepth(options) {
|
|
246
|
+
const limit = options.depth;
|
|
247
|
+
assertInteger("maxDepth: depth", limit, 1);
|
|
248
|
+
return (context) => {
|
|
249
|
+
const walk = {
|
|
250
|
+
getFragment: (name) => context.getFragment(name),
|
|
251
|
+
fragmentDepths: new Map,
|
|
252
|
+
measuring: new Set,
|
|
253
|
+
ignoreIntrospection: options.ignoreIntrospection ?? false
|
|
254
|
+
};
|
|
255
|
+
return {
|
|
256
|
+
OperationDefinition(operation) {
|
|
257
|
+
const depth = depthOf(walk, operation.selectionSet);
|
|
258
|
+
if (depth > limit) {
|
|
259
|
+
context.reportError(new GraphQLError3(`${operationLabel(operation)} is ${depth} levels deep, more than the ${limit} allowed`, {
|
|
260
|
+
nodes: [operation],
|
|
261
|
+
extensions: { code: "QUERY_TOO_DEEP", depth, limit }
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// src/limits/max-fields.ts
|
|
270
|
+
import {
|
|
271
|
+
GraphQLError as GraphQLError4
|
|
272
|
+
} from "graphql";
|
|
273
|
+
function add(a, b) {
|
|
274
|
+
return Math.min(a + b, Number.MAX_SAFE_INTEGER);
|
|
275
|
+
}
|
|
276
|
+
function countOf(walk, selectionSet) {
|
|
277
|
+
let total = 0;
|
|
278
|
+
for (const selection of selectionSet.selections) {
|
|
279
|
+
if (selection.kind === "Field") {
|
|
280
|
+
if (walk.ignoreIntrospection && isIntrospectionRoot(selection.name.value))
|
|
281
|
+
continue;
|
|
282
|
+
total = add(total, 1);
|
|
283
|
+
if (selection.selectionSet)
|
|
284
|
+
total = add(total, countOf(walk, selection.selectionSet));
|
|
285
|
+
} else if (selection.kind === "InlineFragment") {
|
|
286
|
+
total = add(total, countOf(walk, selection.selectionSet));
|
|
287
|
+
} else {
|
|
288
|
+
total = add(total, fragmentCount(walk, selection.name.value));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return total;
|
|
292
|
+
}
|
|
293
|
+
function fragmentCount(walk, name) {
|
|
294
|
+
const known = walk.fragmentCounts.get(name);
|
|
295
|
+
if (known !== undefined)
|
|
296
|
+
return known;
|
|
297
|
+
const fragment = walk.getFragment(name);
|
|
298
|
+
if (!fragment || walk.counting.has(name))
|
|
299
|
+
return 0;
|
|
300
|
+
walk.counting.add(name);
|
|
301
|
+
const result = countOf(walk, fragment.selectionSet);
|
|
302
|
+
walk.counting.delete(name);
|
|
303
|
+
walk.fragmentCounts.set(name, result);
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
function maxFields(options) {
|
|
307
|
+
const limit = options.limit;
|
|
308
|
+
assertInteger("maxFields: limit", limit, 1);
|
|
309
|
+
return (context) => {
|
|
310
|
+
const walk = {
|
|
311
|
+
getFragment: (name) => context.getFragment(name),
|
|
312
|
+
fragmentCounts: new Map,
|
|
313
|
+
counting: new Set,
|
|
314
|
+
ignoreIntrospection: options.ignoreIntrospection ?? false
|
|
315
|
+
};
|
|
316
|
+
return {
|
|
317
|
+
OperationDefinition(operation) {
|
|
318
|
+
const fields = countOf(walk, operation.selectionSet);
|
|
319
|
+
if (fields > limit) {
|
|
320
|
+
context.reportError(new GraphQLError4(`${operationLabel(operation)} selects ${fields} fields, more than the ${limit} allowed`, {
|
|
321
|
+
nodes: [operation],
|
|
322
|
+
extensions: { code: "QUERY_TOO_WIDE", fields, limit }
|
|
323
|
+
}));
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
// src/limits/max-list-cost.ts
|
|
331
|
+
import {
|
|
332
|
+
GraphQLError as GraphQLError5,
|
|
333
|
+
getNamedType,
|
|
334
|
+
getNullableType,
|
|
335
|
+
isListType
|
|
336
|
+
} from "graphql";
|
|
337
|
+
|
|
338
|
+
// src/limits/cost-context.ts
|
|
339
|
+
import {
|
|
340
|
+
isInterfaceType as isInterfaceType2,
|
|
341
|
+
isObjectType as isObjectType2
|
|
342
|
+
} from "graphql";
|
|
343
|
+
var CEILING = Number.MAX_SAFE_INTEGER;
|
|
344
|
+
var add2 = (a, b) => Math.min(CEILING, a + b);
|
|
345
|
+
var multiply = (a, b) => Math.min(CEILING, a * b);
|
|
346
|
+
function fieldOf(cost, parent, name) {
|
|
347
|
+
if (parent === cost.schema.getQueryType() && isIntrospectionRoot(name)) {
|
|
348
|
+
return introspectionRoot(name);
|
|
349
|
+
}
|
|
350
|
+
return isObjectType2(parent) || isInterfaceType2(parent) ? parent.getFields()[name] : undefined;
|
|
351
|
+
}
|
|
352
|
+
function rootType(schema, operation) {
|
|
353
|
+
const type = operation.operation === "query" ? schema.getQueryType() : operation.operation === "mutation" ? schema.getMutationType() : schema.getSubscriptionType();
|
|
354
|
+
return type ?? undefined;
|
|
355
|
+
}
|
|
356
|
+
var EXACT_SIZES = 8;
|
|
357
|
+
var NO_PENDING = -1;
|
|
358
|
+
function memoSize(known, pending) {
|
|
359
|
+
if (pending === undefined || known.has(pending) || known.size < EXACT_SIZES) {
|
|
360
|
+
return pending;
|
|
361
|
+
}
|
|
362
|
+
let rounded = 2 ** Math.ceil(Math.log2(Math.max(pending, 1)));
|
|
363
|
+
if (rounded < pending)
|
|
364
|
+
rounded *= 2;
|
|
365
|
+
return Math.min(CEILING, rounded);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/limits/list-size.ts
|
|
369
|
+
import {
|
|
370
|
+
isAbstractType
|
|
371
|
+
} from "graphql";
|
|
372
|
+
var CEILING2 = Number.MAX_SAFE_INTEGER;
|
|
373
|
+
var CANONICAL_INTEGER = /^(0|[1-9]\d*)$/;
|
|
374
|
+
function sizeOf(value) {
|
|
375
|
+
if (typeof value === "string") {
|
|
376
|
+
return CANONICAL_INTEGER.test(value) ? Math.min(Number(value), CEILING2) : undefined;
|
|
377
|
+
}
|
|
378
|
+
if (typeof value === "bigint") {
|
|
379
|
+
return value >= 0n ? Math.min(Number(value), CEILING2) : undefined;
|
|
380
|
+
}
|
|
381
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? Math.min(value, CEILING2) : undefined;
|
|
382
|
+
}
|
|
383
|
+
function literalSize(value) {
|
|
384
|
+
if (value.kind === "IntValue" || value.kind === "FloatValue") {
|
|
385
|
+
return sizeOf(Number(value.value));
|
|
386
|
+
}
|
|
387
|
+
return value.kind === "StringValue" ? sizeOf(value.value) : undefined;
|
|
388
|
+
}
|
|
389
|
+
function defaultSizeOf(argument) {
|
|
390
|
+
if (!argument)
|
|
391
|
+
return;
|
|
392
|
+
if ("defaultValue" in argument) {
|
|
393
|
+
const size = sizeOf(argument.defaultValue);
|
|
394
|
+
if (size !== undefined)
|
|
395
|
+
return size;
|
|
396
|
+
}
|
|
397
|
+
if (!("default" in argument))
|
|
398
|
+
return;
|
|
399
|
+
const fallback = argument.default;
|
|
400
|
+
if (typeof fallback !== "object" || fallback === null)
|
|
401
|
+
return;
|
|
402
|
+
if ("value" in fallback)
|
|
403
|
+
return sizeOf(fallback.value);
|
|
404
|
+
if ("literal" in fallback && typeof fallback.literal === "object") {
|
|
405
|
+
const literal = fallback.literal;
|
|
406
|
+
return literal !== null && "kind" in literal ? literalSize(literal) : undefined;
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
var profiles = new WeakMap;
|
|
411
|
+
function profileOf(cost, parent, definition) {
|
|
412
|
+
let bySizes = profiles.get(cost.schema);
|
|
413
|
+
if (!bySizes) {
|
|
414
|
+
bySizes = new WeakMap;
|
|
415
|
+
profiles.set(cost.schema, bySizes);
|
|
416
|
+
}
|
|
417
|
+
let byField = bySizes.get(cost.sizeArguments);
|
|
418
|
+
if (!byField) {
|
|
419
|
+
byField = new Map;
|
|
420
|
+
bySizes.set(cost.sizeArguments, byField);
|
|
421
|
+
}
|
|
422
|
+
const key = `${parent?.name}.${definition.name}`;
|
|
423
|
+
const known = byField.get(key);
|
|
424
|
+
if (known)
|
|
425
|
+
return known;
|
|
426
|
+
const definitions = [definition];
|
|
427
|
+
if (isAbstractType(parent)) {
|
|
428
|
+
for (const type of cost.schema.getPossibleTypes(parent)) {
|
|
429
|
+
const implemented = type.getFields()[definition.name];
|
|
430
|
+
if (implemented)
|
|
431
|
+
definitions.push(implemented);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const profile = {
|
|
435
|
+
largest: cost.sizeArguments.map(() => {
|
|
436
|
+
return;
|
|
437
|
+
}),
|
|
438
|
+
someLack: cost.sizeArguments.map(() => false),
|
|
439
|
+
someBare: false,
|
|
440
|
+
declares: false,
|
|
441
|
+
someUndeclared: false
|
|
442
|
+
};
|
|
443
|
+
for (const field of definitions) {
|
|
444
|
+
let any = false;
|
|
445
|
+
cost.sizeArguments.forEach((name, index) => {
|
|
446
|
+
const size = defaultSizeOf(field.args.find((arg) => arg.name === name));
|
|
447
|
+
if (size === undefined) {
|
|
448
|
+
profile.someLack[index] = true;
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
any = true;
|
|
452
|
+
profile.largest[index] = Math.max(profile.largest[index] ?? 0, size);
|
|
453
|
+
});
|
|
454
|
+
if (!any)
|
|
455
|
+
profile.someBare = true;
|
|
456
|
+
if (field.args.some((arg) => cost.sizeArguments.includes(arg.name))) {
|
|
457
|
+
profile.declares = true;
|
|
458
|
+
} else {
|
|
459
|
+
profile.someUndeclared = true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
byField.set(key, profile);
|
|
463
|
+
return profile;
|
|
464
|
+
}
|
|
465
|
+
function argumentSize(cost, profile, index, node) {
|
|
466
|
+
const largest = profile.largest[index];
|
|
467
|
+
if (!node)
|
|
468
|
+
return largest;
|
|
469
|
+
const literal = literalSize(node.value);
|
|
470
|
+
if (literal !== undefined)
|
|
471
|
+
return literal;
|
|
472
|
+
return profile.someLack[index] ? Math.max(largest ?? 0, cost.defaultFirst) : largest;
|
|
473
|
+
}
|
|
474
|
+
function statedSize(cost, parent, definition, args) {
|
|
475
|
+
const profile = profileOf(cost, parent, definition);
|
|
476
|
+
let size;
|
|
477
|
+
let given = false;
|
|
478
|
+
cost.sizeArguments.forEach((name, index) => {
|
|
479
|
+
const node = args?.find((arg) => arg.name.value === name);
|
|
480
|
+
if (node)
|
|
481
|
+
given = true;
|
|
482
|
+
const said = argumentSize(cost, profile, index, node);
|
|
483
|
+
if (said !== undefined)
|
|
484
|
+
size = Math.max(size ?? 0, said);
|
|
485
|
+
});
|
|
486
|
+
if (size !== undefined && !given && profile.someBare) {
|
|
487
|
+
return Math.max(size, cost.defaultFirst);
|
|
488
|
+
}
|
|
489
|
+
return size;
|
|
490
|
+
}
|
|
491
|
+
function declaration(cost, parent, definition) {
|
|
492
|
+
const { declares, someUndeclared } = profileOf(cost, parent, definition);
|
|
493
|
+
if (!declares)
|
|
494
|
+
return "none";
|
|
495
|
+
return someUndeclared ? "some" : "all";
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/limits/pending-size.ts
|
|
499
|
+
function listSizeUnder(cost, parent, definition, args, pending) {
|
|
500
|
+
const own = () => statedSize(cost, parent, definition, args) ?? cost.defaultFirst;
|
|
501
|
+
if (pending === undefined)
|
|
502
|
+
return own();
|
|
503
|
+
const declared = declaration(cost, parent, definition);
|
|
504
|
+
if (declared === "none")
|
|
505
|
+
return pending;
|
|
506
|
+
if (declared === "all")
|
|
507
|
+
return own();
|
|
508
|
+
return Math.max(pending, own());
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/limits/max-list-cost.ts
|
|
512
|
+
var DEFAULT_SIZE_ARGUMENTS = ["first", "last", "limit"];
|
|
513
|
+
function fieldCost(cost, parent, selection, definition, pending) {
|
|
514
|
+
if (!selection.selectionSet)
|
|
515
|
+
return 1;
|
|
516
|
+
const name = selection.name.value;
|
|
517
|
+
const type = getNamedType(definition.type);
|
|
518
|
+
if (!isListType(getNullableType(definition.type))) {
|
|
519
|
+
const stated = statedSize(cost, parent, definition, selection.arguments);
|
|
520
|
+
const inner = costOf(cost, selection.selectionSet, type, stated);
|
|
521
|
+
return add2(1, inner);
|
|
522
|
+
}
|
|
523
|
+
const inner = costOf(cost, selection.selectionSet, type, undefined);
|
|
524
|
+
const size = parent?.name.startsWith("__") ? introspectionSize(cost.schema, name) : listSizeUnder(cost, parent, definition, selection.arguments, pending);
|
|
525
|
+
return add2(1, multiply(inner, size));
|
|
526
|
+
}
|
|
527
|
+
function costOf(cost, selectionSet, parent, pending) {
|
|
528
|
+
let total = 0;
|
|
529
|
+
for (const selection of selectionSet.selections) {
|
|
530
|
+
if (selection.kind === "Field") {
|
|
531
|
+
const name = selection.name.value;
|
|
532
|
+
if (name === "__typename")
|
|
533
|
+
continue;
|
|
534
|
+
if (isIntrospectionRoot(name) && cost.ignoreIntrospection)
|
|
535
|
+
continue;
|
|
536
|
+
const definition = fieldOf(cost, parent, name);
|
|
537
|
+
if (!definition)
|
|
538
|
+
continue;
|
|
539
|
+
total = add2(total, fieldCost(cost, parent, selection, definition, pending));
|
|
540
|
+
} else if (selection.kind === "InlineFragment") {
|
|
541
|
+
const condition = selection.typeCondition ? cost.schema.getType(selection.typeCondition.name.value) : parent;
|
|
542
|
+
total = add2(total, costOf(cost, selection.selectionSet, condition, pending));
|
|
543
|
+
} else {
|
|
544
|
+
total = add2(total, fragmentCost(cost, selection.name.value, pending));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return total;
|
|
548
|
+
}
|
|
549
|
+
function fragmentCost(cost, name, pending) {
|
|
550
|
+
let known = cost.fragmentCosts.get(name);
|
|
551
|
+
if (!known) {
|
|
552
|
+
known = new Map;
|
|
553
|
+
cost.fragmentCosts.set(name, known);
|
|
554
|
+
}
|
|
555
|
+
const size = memoSize(known, pending);
|
|
556
|
+
const key = size ?? NO_PENDING;
|
|
557
|
+
const memo = known.get(key);
|
|
558
|
+
if (memo !== undefined)
|
|
559
|
+
return memo;
|
|
560
|
+
const fragment = cost.getFragment(name);
|
|
561
|
+
if (!fragment || cost.costing.has(name))
|
|
562
|
+
return 0;
|
|
563
|
+
cost.costing.add(name);
|
|
564
|
+
const result = costOf(cost, fragment.selectionSet, cost.schema.getType(fragment.typeCondition.name.value), size);
|
|
565
|
+
cost.costing.delete(name);
|
|
566
|
+
known.set(key, result);
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
function maxListCost(options) {
|
|
570
|
+
const { budget, defaultFirst } = options;
|
|
571
|
+
const sizeArguments = Object.freeze([
|
|
572
|
+
...options.sizeArguments ?? DEFAULT_SIZE_ARGUMENTS
|
|
573
|
+
]);
|
|
574
|
+
assertInteger("maxListCost: budget", budget, 0);
|
|
575
|
+
assertInteger("maxListCost: defaultFirst", defaultFirst, 0);
|
|
576
|
+
return (context) => {
|
|
577
|
+
const schema = context.getSchema();
|
|
578
|
+
const cost = {
|
|
579
|
+
schema,
|
|
580
|
+
sizeArguments,
|
|
581
|
+
defaultFirst,
|
|
582
|
+
ignoreIntrospection: options.ignoreIntrospection ?? false,
|
|
583
|
+
getFragment: (name) => context.getFragment(name),
|
|
584
|
+
fragmentCosts: new Map,
|
|
585
|
+
costing: new Set
|
|
586
|
+
};
|
|
587
|
+
return {
|
|
588
|
+
OperationDefinition(operation) {
|
|
589
|
+
const total = costOf(cost, operation.selectionSet, rootType(schema, operation), undefined);
|
|
590
|
+
if (total > budget) {
|
|
591
|
+
context.reportError(new GraphQLError5(`${operationLabel(operation)} costs ${total}, more than the ${budget} allowed`, {
|
|
592
|
+
nodes: [operation],
|
|
593
|
+
extensions: {
|
|
594
|
+
code: "QUERY_TOO_COSTLY",
|
|
595
|
+
cost: total,
|
|
596
|
+
limit: budget
|
|
597
|
+
}
|
|
598
|
+
}));
|
|
599
|
+
}
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
};
|
|
604
|
+
}
|
|
128
605
|
// src/validated.ts
|
|
129
606
|
import { z } from "zod";
|
|
130
607
|
function validated(schema, resolve) {
|
|
@@ -145,21 +622,21 @@ import {
|
|
|
145
622
|
} from "graphql";
|
|
146
623
|
var wrapped = Symbol.for("@nxgt/graphql-validation/wrapped");
|
|
147
624
|
var wrappedWith = Symbol.for("@nxgt/graphql-validation/formats");
|
|
148
|
-
function checking(schema, where, resolve) {
|
|
625
|
+
function checking(schema, where, resolve, constraintOf) {
|
|
149
626
|
const check = async (source, args, context, info) => await resolve(source, await parseArgs(schema, args, where, constraintOf), context, info);
|
|
150
627
|
check[wrapped] = true;
|
|
151
628
|
return check;
|
|
152
629
|
}
|
|
153
|
-
function wrap(field, args, where, subscription) {
|
|
630
|
+
function wrap(field, args, where, subscription, constraintOf) {
|
|
154
631
|
const subscribe = field.subscribe ?? (subscription ? defaultFieldResolver : undefined);
|
|
155
632
|
if (subscribe) {
|
|
156
633
|
if (!subscribe[wrapped])
|
|
157
|
-
field.subscribe = checking(args, where, subscribe);
|
|
634
|
+
field.subscribe = checking(args, where, subscribe, constraintOf);
|
|
158
635
|
return;
|
|
159
636
|
}
|
|
160
637
|
const resolve = field.resolve ?? defaultFieldResolver;
|
|
161
638
|
if (!resolve[wrapped])
|
|
162
|
-
field.resolve = checking(args, where, resolve);
|
|
639
|
+
field.resolve = checking(args, where, resolve, constraintOf);
|
|
163
640
|
}
|
|
164
641
|
function withValidation(schema, options = {}) {
|
|
165
642
|
const formats = options.formats ?? {};
|
|
@@ -168,9 +645,16 @@ function withValidation(schema, options = {}) {
|
|
|
168
645
|
if (before && !sameFormats(before, formats)) {
|
|
169
646
|
throw new Error("withValidation: this schema is already wrapped with other formats. Call withValidation once, with every format.");
|
|
170
647
|
}
|
|
171
|
-
const directive = checkConstraints2(schema, (type, field, args) => {
|
|
648
|
+
const directive = checkConstraints2(schema, (type, field, args, declared) => {
|
|
172
649
|
const subscription = type === schema.getSubscriptionType();
|
|
173
|
-
|
|
650
|
+
const named = (issue) => {
|
|
651
|
+
const argument = constraintOf(issue);
|
|
652
|
+
if (!argument)
|
|
653
|
+
return;
|
|
654
|
+
const found = declared(issue.path).find(({ rule }) => rule.argument === argument);
|
|
655
|
+
return found && { argument, value: found.value };
|
|
656
|
+
};
|
|
657
|
+
wrap(field, args, `${type.name}.${field.name}`, subscription, named);
|
|
174
658
|
}, formats);
|
|
175
659
|
if (!directive) {
|
|
176
660
|
throw new Error("withValidation: this schema declares no @constraint directive. Add constraintTypeDefs to its type definitions.");
|
|
@@ -192,9 +676,12 @@ export {
|
|
|
192
676
|
fieldsError,
|
|
193
677
|
inputPath,
|
|
194
678
|
invalidInputError,
|
|
679
|
+
maxDepth,
|
|
680
|
+
maxFields,
|
|
681
|
+
maxListCost,
|
|
195
682
|
validated,
|
|
196
683
|
withValidation
|
|
197
684
|
};
|
|
198
685
|
|
|
199
|
-
//# debugId=
|
|
686
|
+
//# debugId=3B937DADAE820B8964756E2164756E21
|
|
200
687
|
//# sourceMappingURL=index.js.map
|