@nxgt/graphql-validation 0.2.0 → 0.3.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 +63 -6
- package/dist/bad-user-input.d.ts +1 -1
- package/dist/builder/args-schema.d.ts +1 -1
- package/dist/builder/check-constraints.d.ts +6 -1
- package/dist/builder/check-constraints.d.ts.map +1 -1
- package/dist/builder/constraints.d.ts +1 -1
- package/dist/builder/input-code.d.ts +15 -2
- package/dist/builder/input-code.d.ts.map +1 -1
- package/dist/builder/input-schema.d.ts +3 -1
- package/dist/builder/input-schema.d.ts.map +1 -1
- package/dist/builder/leaf.d.ts +3 -3
- package/dist/builder/leaf.d.ts.map +1 -1
- package/dist/chunks/{index-s1kkmg35.js → index-1mzzydy3.js} +219 -23
- package/dist/chunks/index-1mzzydy3.js.map +25 -0
- package/dist/chunks/{index-rbxe5m3y.js → index-xcaf9kes.js} +24 -97
- package/dist/chunks/index-xcaf9kes.js.map +27 -0
- package/dist/cli.js +1 -1
- package/dist/codegen.d.ts +4 -3
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +19 -9
- package/dist/codegen.js.map +3 -3
- package/dist/formats/all.d.ts +8 -8
- package/dist/formats/byte.d.ts +1 -1
- package/dist/formats/byte.d.ts.map +1 -1
- package/dist/formats/date-time.d.ts +1 -1
- package/dist/formats/date-time.d.ts.map +1 -1
- package/dist/formats/date.d.ts +1 -1
- package/dist/formats/date.d.ts.map +1 -1
- package/dist/formats/email.d.ts +1 -1
- package/dist/formats/email.d.ts.map +1 -1
- package/dist/formats/format.d.ts +9 -3
- package/dist/formats/format.d.ts.map +1 -1
- package/dist/formats/index.d.ts +7 -9
- package/dist/formats/index.d.ts.map +1 -1
- package/dist/formats/ipv4.d.ts +1 -1
- package/dist/formats/ipv4.d.ts.map +1 -1
- package/dist/formats/ipv6.d.ts +1 -1
- package/dist/formats/ipv6.d.ts.map +1 -1
- package/dist/formats/registry.d.ts +50 -0
- package/dist/formats/registry.d.ts.map +1 -0
- package/dist/formats/uri.d.ts +1 -1
- package/dist/formats/uri.d.ts.map +1 -1
- package/dist/formats/uuid.d.ts +1 -1
- package/dist/formats/uuid.d.ts.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -5
- package/dist/index.js.map +3 -3
- package/dist/rules/all.d.ts +15 -15
- package/dist/rules/contains.d.ts +1 -1
- package/dist/rules/ends-with.d.ts +1 -1
- package/dist/rules/exclusive-max.d.ts +1 -1
- package/dist/rules/exclusive-min.d.ts +1 -1
- package/dist/rules/format.d.ts +1 -1
- package/dist/rules/format.d.ts.map +1 -1
- package/dist/rules/index.d.ts +9 -4
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/max-items.d.ts +1 -1
- package/dist/rules/max-length.d.ts +1 -1
- package/dist/rules/max.d.ts +1 -1
- package/dist/rules/min-items.d.ts +1 -1
- package/dist/rules/min-length.d.ts +1 -1
- package/dist/rules/min.d.ts +1 -1
- package/dist/rules/multiple-of.d.ts +1 -1
- package/dist/rules/not-contains.d.ts +1 -1
- package/dist/rules/pattern.d.ts +1 -1
- package/dist/rules/rule.d.ts +20 -3
- package/dist/rules/rule.d.ts.map +1 -1
- package/dist/rules/starts-with.d.ts +1 -1
- package/dist/with-validation.d.ts +17 -1
- package/dist/with-validation.d.ts.map +1 -1
- package/docs/README.md +1 -1
- package/docs/guide/constraints.md +133 -4
- package/docs/guide/errors.md +4 -1
- package/docs/roadmap.md +21 -15
- package/docs/troubleshooting.md +165 -6
- package/package.json +2 -2
- package/dist/chunks/index-rbxe5m3y.js.map +0 -37
- package/dist/chunks/index-s1kkmg35.js.map +0 -14
package/dist/formats/ipv6.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const ipv6Format: import("./format").
|
|
1
|
+
export declare const ipv6Format: import("./format.js").BuiltInFormat<"ipv6">;
|
|
2
2
|
//# sourceMappingURL=ipv6.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ipv6.d.ts","sourceRoot":"","sources":["../../src/formats/ipv6.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"ipv6.d.ts","sourceRoot":"","sources":["../../src/formats/ipv6.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,0CAIrB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Format, StringSchema } from './format.js';
|
|
2
|
+
import { type FormatName } from './index.js';
|
|
3
|
+
/**
|
|
4
|
+
* The application's own formats, keyed by the name `@constraint(format:
|
|
5
|
+
* "...")` gives them: each a Zod string schema, `z.string()` or a string
|
|
6
|
+
* format (`z.email()`), narrowed (`.regex()`, `.refine()`) but never
|
|
7
|
+
* transformed nor rewritten (`.trim()`, `.toLowerCase()`, `z.url()`,
|
|
8
|
+
* `z.coerce.string()`), so the resolver receives what was sent.
|
|
9
|
+
*/
|
|
10
|
+
export type FormatSchemas = {
|
|
11
|
+
readonly [name: string]: StringSchema;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* `F` with every key that names a built-in format typed `never`, so
|
|
15
|
+
* replacing one is a type error before it is a startup error. The keys are
|
|
16
|
+
* optional: a record typed wider than its literal (`FormatSchemas`,
|
|
17
|
+
* `Record<string, z.ZodString>`) may hold none of them, and passes.
|
|
18
|
+
*/
|
|
19
|
+
export type OwnFormats<F extends FormatSchemas> = F & {
|
|
20
|
+
readonly [K in keyof F & FormatName]?: never;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Every format `@constraint(format: "...")` can name: the built-in ones,
|
|
24
|
+
* then the application's own. A lookup is one `Map` read, however many
|
|
25
|
+
* formats there are.
|
|
26
|
+
*/
|
|
27
|
+
export declare class FormatRegistry {
|
|
28
|
+
#private;
|
|
29
|
+
/**
|
|
30
|
+
* Fails, naming the format, on a bad name, a built-in's name, a schema
|
|
31
|
+
* that is not a string's, one zod cannot run, or one that rewrites the
|
|
32
|
+
* value.
|
|
33
|
+
*/
|
|
34
|
+
constructor(own?: FormatSchemas);
|
|
35
|
+
/** The format named `name`, or an error that lists the known ones. */
|
|
36
|
+
named(name: string): Format;
|
|
37
|
+
/**
|
|
38
|
+
* The format of the application that last went async, then forgotten: read
|
|
39
|
+
* right after a synchronous parse threw zod's `$ZodAsyncError`, it names the
|
|
40
|
+
* format that made it throw (zod stops at the first check that returns a
|
|
41
|
+
* promise, and only an application's format can).
|
|
42
|
+
*/
|
|
43
|
+
takeAsync(): string | undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The registry of a set of formats, built (and checked) once per record: a
|
|
47
|
+
* code generator hands the same record for every field.
|
|
48
|
+
*/
|
|
49
|
+
export declare function registryOf(own?: FormatSchemas): FormatRegistry;
|
|
50
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/formats/registry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAAA;CAAE,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,aAAa,IAAI,CAAC,GAAG;IACrD,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,KAAK;CAC5C,CAAC;AAIF;;;;GAIG;AACH,qBAAa,cAAc;;IAI1B;;;;OAIG;gBACS,GAAG,GAAE,aAAkB;IAoCnC,sEAAsE;IACtE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAQ3B;;;;;OAKG;IACH,SAAS,IAAI,MAAM,GAAG,SAAS;CAK/B;AA4HD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,cAAc,CAQ9D"}
|
package/dist/formats/uri.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const uriFormat: import("./format").
|
|
1
|
+
export declare const uriFormat: import("./format.js").BuiltInFormat<"uri">;
|
|
2
2
|
//# sourceMappingURL=uri.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uri.d.ts","sourceRoot":"","sources":["../../src/formats/uri.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,SAAS,
|
|
1
|
+
{"version":3,"file":"uri.d.ts","sourceRoot":"","sources":["../../src/formats/uri.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,SAAS,yCAIpB,CAAC"}
|
package/dist/formats/uuid.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const uuidFormat: import("./format").
|
|
1
|
+
export declare const uuidFormat: import("./format.js").BuiltInFormat<"uuid">;
|
|
2
2
|
//# sourceMappingURL=uuid.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../../src/formats/uuid.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../../src/formats/uuid.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,UAAU,0CAIrB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
export { type BadUserInputExtensions, badUserInput, type ValidationIssue, } from './bad-user-input';
|
|
2
|
-
export { constraintTypeDefs } from './constraint-directive';
|
|
3
|
-
export type {
|
|
4
|
-
export
|
|
5
|
-
export {
|
|
1
|
+
export { type BadUserInputExtensions, badUserInput, type ValidationIssue, } from './bad-user-input.js';
|
|
2
|
+
export { constraintTypeDefs } from './constraint-directive.js';
|
|
3
|
+
export type { FormatName } from './formats/index.js';
|
|
4
|
+
export type { StringSchema } from './formats/format.js';
|
|
5
|
+
export type { FormatSchemas, OwnFormats } from './formats/registry.js';
|
|
6
|
+
export type { ConstraintArgument } from './rules/index.js';
|
|
7
|
+
export { type ArgsSchema, type SchemaOf, validated } from './validated.js';
|
|
8
|
+
export { type WithValidationOptions, withValidation, } from './with-validation.js';
|
|
6
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,sBAAsB,EAC3B,YAAY,EACZ,KAAK,eAAe,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,sBAAsB,EAC3B,YAAY,EACZ,KAAK,eAAe,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACpE,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EACN,KAAK,qBAAqB,EAC1B,cAAc,GACd,MAAM,mBAAmB,CAAC"}
|
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-xcaf9kes.js";
|
|
5
5
|
import {
|
|
6
6
|
checkConstraints2
|
|
7
|
-
} from "./chunks/index-
|
|
7
|
+
} from "./chunks/index-1mzzydy3.js";
|
|
8
8
|
|
|
9
9
|
// src/bad-user-input.ts
|
|
10
10
|
import { GraphQLError } from "graphql";
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
defaultFieldResolver
|
|
48
48
|
} from "graphql";
|
|
49
49
|
var wrapped = Symbol.for("@nxgt/graphql-validation/wrapped");
|
|
50
|
+
var wrappedWith = Symbol.for("@nxgt/graphql-validation/formats");
|
|
50
51
|
function checking(schema, where, resolve) {
|
|
51
52
|
const check = async (source, args, context, info) => await resolve(source, await parseArgs(schema, args, where, constraintOf), context, info);
|
|
52
53
|
check[wrapped] = true;
|
|
@@ -63,16 +64,28 @@ function wrap(field, args, where, subscription) {
|
|
|
63
64
|
if (!resolve[wrapped])
|
|
64
65
|
field.resolve = checking(args, where, resolve);
|
|
65
66
|
}
|
|
66
|
-
function withValidation(schema) {
|
|
67
|
+
function withValidation(schema, options = {}) {
|
|
68
|
+
const formats = options.formats ?? {};
|
|
69
|
+
const holder = schema;
|
|
70
|
+
const before = holder[wrappedWith];
|
|
71
|
+
if (before && !sameFormats(before, formats)) {
|
|
72
|
+
throw new Error("withValidation: this schema is already wrapped with other formats. Call withValidation once, with every format.");
|
|
73
|
+
}
|
|
67
74
|
const directive = checkConstraints2(schema, (type, field, args) => {
|
|
68
75
|
const subscription = type === schema.getSubscriptionType();
|
|
69
76
|
wrap(field, args, `${type.name}.${field.name}`, subscription);
|
|
70
|
-
});
|
|
77
|
+
}, formats);
|
|
71
78
|
if (!directive) {
|
|
72
79
|
throw new Error("withValidation: this schema declares no @constraint directive. Add constraintTypeDefs to its type definitions.");
|
|
73
80
|
}
|
|
81
|
+
if (!before)
|
|
82
|
+
Object.defineProperty(schema, wrappedWith, { value: formats });
|
|
74
83
|
return schema;
|
|
75
84
|
}
|
|
85
|
+
function sameFormats(a, b) {
|
|
86
|
+
const names = Object.keys(a);
|
|
87
|
+
return names.length === Object.keys(b).length && names.every((name) => Object.hasOwn(b, name) && a[name] === b[name]);
|
|
88
|
+
}
|
|
76
89
|
export {
|
|
77
90
|
badUserInput,
|
|
78
91
|
constraintTypeDefs2 as constraintTypeDefs,
|
|
@@ -80,5 +93,5 @@ export {
|
|
|
80
93
|
withValidation
|
|
81
94
|
};
|
|
82
95
|
|
|
83
|
-
//# debugId=
|
|
96
|
+
//# debugId=B588BD3EF63841CA64756E2164756E21
|
|
84
97
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import { GraphQLError } from 'graphql';\nimport type { z } from 'zod';\nimport type { ConstraintArgument } from './rules';\n\n/** One reason an input was refused, at its path inside the arguments. */\nexport interface ValidationIssue {\n\t/** `['input', 'address', 'zip']`: the argument, then the input fields. */\n\treadonly path: readonly (string | number)[];\n\treadonly message: string;\n\t/** Zod's issue code: `too_small`, `invalid_format`, `custom`, ... */\n\treadonly code: string;\n\t/**\n\t * The `@constraint` argument that refused (`minLength`, `format`), when a\n\t * directive did: stable across Zod versions, unlike `code`. Absent for a\n\t * refusal of `validated()`'s own schema.\n\t */\n\treadonly constraint?: ConstraintArgument;\n}\n\n/** The `extensions` of the error an invalid input becomes. */\nexport type BadUserInputExtensions = {\n\treadonly code: 'BAD_USER_INPUT';\n\treadonly issues: readonly ValidationIssue[];\n};\n\n/** Names the `@constraint` argument behind an issue, when there is one. */\nexport type ConstraintOf = (\n\tissue: z.core.$ZodIssue,\n) => ConstraintArgument | undefined;\n\nfunction issuesOf(\n\terror: z.ZodError,\n\tconstraintOf?: ConstraintOf,\n): ValidationIssue[] {\n\treturn error.issues.map((issue) => {\n\t\tconst path = issue.path.map((key) =>\n\t\t\ttypeof key === 'number' ? key : String(key),\n\t\t);\n\t\tconst constraint = constraintOf?.(issue);\n\t\treturn constraint\n\t\t\t? { path, message: issue.message, code: issue.code, constraint }\n\t\t\t: { path, message: issue.message, code: issue.code };\n\t});\n}\n\nfunction errorOf(where: string, issues: ValidationIssue[]): GraphQLError {\n\tconst [first] = issues;\n\tconst reason = first\n\t\t? `${first.path.join('.') || 'arguments'}: ${first.message}`\n\t\t: 'refused';\n\tconst extensions: BadUserInputExtensions = { code: 'BAD_USER_INPUT', issues };\n\treturn new GraphQLError(`Invalid arguments for ${where}. ${reason}`, {\n\t\textensions,\n\t});\n}\n\n/**\n * The one error an invalid input becomes: `extensions.code` is\n * `BAD_USER_INPUT`, the code Apollo and most clients already read, and\n * `extensions.issues` lists every reason with its path, so a form can show\n * each one beside its field. The message names the field and the first\n * reason, for a log line.\n */\nexport function badUserInput(where: string, error: z.ZodError): GraphQLError {\n\treturn errorOf(where, issuesOf(error));\n}\n\n/** The parsed arguments, or the error above, its issues named by `constraintOf`. */\nexport async function parseArgs(\n\tschema: z.ZodType,\n\targs: unknown,\n\twhere: string,\n\tconstraintOf?: ConstraintOf,\n): Promise<unknown> {\n\tconst result = await schema.safeParseAsync(args);\n\tif (!result.success)\n\t\tthrow errorOf(where, issuesOf(result.error, constraintOf));\n\treturn result.data;\n}\n",
|
|
6
6
|
"import type { GraphQLFieldResolver, GraphQLResolveInfo } from 'graphql';\nimport { z } from 'zod';\nimport { parseArgs } from './bad-user-input';\n\n/** An object schema of the arguments, or its shape. */\nexport type ArgsSchema = z.ZodType<Record<string, unknown>> | z.ZodRawShape;\n\n/** The schema `ArgsSchema` stands for. */\nexport type SchemaOf<S extends ArgsSchema> = S extends z.ZodType\n\t? S\n\t: z.ZodObject<S & z.ZodRawShape>;\n\n/**\n * A resolver whose arguments are checked by `schema` before it runs, for what\n * a `@constraint` cannot say: a rule across arguments, a business refinement,\n * an async check. It receives the parsed arguments (`z.output`), defaults and\n * transforms applied; an invalid input is the same `BAD_USER_INPUT` error as\n * the directives'.\n *\n * ```ts\n * signUp: validated(\n * z.object({ input: signUpSchema }).refine(({ input }) => input.password !== input.email),\n * (_, { input }) => users.create(input),\n * )\n * ```\n */\nexport function validated<\n\tS extends ArgsSchema,\n\tTSource = unknown,\n\tTContext = unknown,\n\tTResult = unknown,\n>(\n\tschema: S,\n\tresolve: (\n\t\tsource: TSource,\n\t\targs: z.output<SchemaOf<S>>,\n\t\tcontext: TContext,\n\t\tinfo: GraphQLResolveInfo,\n\t) => TResult,\n): GraphQLFieldResolver<\n\tTSource,\n\tTContext,\n\tz.input<SchemaOf<S>>,\n\tPromise<Awaited<TResult>>\n> {\n\t// `_zod`, not `instanceof`: a schema from another copy of zod is still one.\n\tconst checked = (\n\t\t'_zod' in schema ? schema : z.object(schema as z.ZodRawShape)\n\t) as z.ZodType;\n\treturn async (source, args, context, info): Promise<Awaited<TResult>> => {\n\t\tconst where = `${info.parentType.name}.${info.fieldName}`;\n\t\tconst parsed = (await parseArgs(checked, args, where)) as z.output<\n\t\t\tSchemaOf<S>\n\t\t>;\n\t\treturn await resolve(source, parsed, context, info);\n\t};\n}\n",
|
|
7
|
-
"import {\n\tdefaultFieldResolver,\n\ttype GraphQLField,\n\ttype GraphQLFieldResolver,\n\ttype GraphQLSchema,\n} from 'graphql';\nimport type { z } from 'zod';\nimport { parseArgs } from './bad-user-input';\nimport { checkConstraints } from './builder/check-constraints';\nimport { constraintOf } from './rules';\n\n// Symbol.for: two copies of the package in one tree still wrap a field once.\nconst wrapped = Symbol.for('@nxgt/graphql-validation/wrapped');\n\ntype Resolver = GraphQLFieldResolver<unknown, unknown> & { [wrapped]?: true };\n\nfunction checking(\n\tschema: z.ZodType,\n\twhere: string,\n\tresolve: Resolver,\n): Resolver {\n\tconst check: Resolver = async (source, args, context, info) =>\n\t\tawait resolve(\n\t\t\tsource,\n\t\t\tawait parseArgs(schema, args, where, constraintOf),\n\t\t\tcontext,\n\t\t\tinfo,\n\t\t);\n\tcheck[wrapped] = true;\n\treturn check;\n}\n\n/**\n * Wraps a field once. A subscription field is checked in `subscribe`, which\n * runs once per subscription — graphql's default one when the field has none\n * — and its `resolve` runs once per event, with the same arguments, already\n * checked.\n */\nfunction wrap(\n\tfield: GraphQLField<unknown, unknown>,\n\targs: z.ZodType,\n\twhere: string,\n\tsubscription: boolean,\n): void {\n\tconst subscribe = (field.subscribe ??\n\t\t(subscription ? defaultFieldResolver : undefined)) as Resolver | undefined;\n\tif (subscribe) {\n\t\tif (!subscribe[wrapped]) field.subscribe = checking(args, where, subscribe);\n\t\treturn;\n\t}\n\tconst resolve = (field.resolve ?? defaultFieldResolver) as Resolver;\n\tif (!resolve[wrapped]) field.resolve = checking(args, where, resolve);\n}\n\n/**\n * Checks every `@constraint` of `schema` before the resolvers run. Each\n * field with a constrained argument — directly, or anywhere inside an input\n * type it takes — gets its resolver (and its `subscribe`) wrapped: the\n * arguments are parsed by the Zod schema the directives describe, the\n * resolver receives the parsed value, and an invalid input becomes one\n * `BAD_USER_INPUT` error. Fields with nothing to check are left untouched.\n *\n * The schema is built here, once: a constraint that cannot apply (a\n * `minLength` on an `Int`, an unknown format, a bad pattern) throws now, not\n * on the first request. The resolvers are wrapped in place and the same\n * schema is returned, so it goes wherever it went before; calling it twice\n * wraps nothing twice. Call it last, once every resolver is attached: a\n * resolver set on a field afterwards replaces the check.\n *\n * A `@constraint` on an interface's argument must be repeated on each\n * object's field, which is the one that resolves; a field that drops it\n * fails here.\n *\n * The directives are read from the SDL (`astNode`), so a schema built from\n * type definitions — `buildSchema`, `makeExecutableSchema`, Yoga, Apollo —\n * is checked; a code-first schema with no SDL has no `@constraint` to read.\n */\nexport function withValidation
|
|
7
|
+
"import {\n\tdefaultFieldResolver,\n\ttype GraphQLField,\n\ttype GraphQLFieldResolver,\n\ttype GraphQLSchema,\n} from 'graphql';\nimport type { z } from 'zod';\nimport { parseArgs } from './bad-user-input';\nimport { checkConstraints } from './builder/check-constraints';\nimport type { FormatSchemas, OwnFormats } from './formats/registry';\nimport { constraintOf } from './rules';\n\n// Symbol.for: two copies of the package in one tree still wrap a field once.\nconst wrapped = Symbol.for('@nxgt/graphql-validation/wrapped');\n// The formats a schema was wrapped with, kept on the schema itself.\nconst wrappedWith = Symbol.for('@nxgt/graphql-validation/formats');\n\n/** What `withValidation` takes besides the schema. */\nexport interface WithValidationOptions<\n\tF extends FormatSchemas = FormatSchemas,\n> {\n\t/**\n\t * The application's own formats, which `@constraint(format: \"...\")` may\n\t * name beside the built-in ones: one Zod string schema each, keyed by its\n\t * name (lowercase letters, digits and hyphens). A built-in name is a type\n\t * error, and refused at startup.\n\t */\n\treadonly formats?: OwnFormats<F>;\n}\n\ntype Resolver = GraphQLFieldResolver<unknown, unknown> & { [wrapped]?: true };\n\nfunction checking(\n\tschema: z.ZodType,\n\twhere: string,\n\tresolve: Resolver,\n): Resolver {\n\tconst check: Resolver = async (source, args, context, info) =>\n\t\tawait resolve(\n\t\t\tsource,\n\t\t\tawait parseArgs(schema, args, where, constraintOf),\n\t\t\tcontext,\n\t\t\tinfo,\n\t\t);\n\tcheck[wrapped] = true;\n\treturn check;\n}\n\n/**\n * Wraps a field once. A subscription field is checked in `subscribe`, which\n * runs once per subscription — graphql's default one when the field has none\n * — and its `resolve` runs once per event, with the same arguments, already\n * checked.\n */\nfunction wrap(\n\tfield: GraphQLField<unknown, unknown>,\n\targs: z.ZodType,\n\twhere: string,\n\tsubscription: boolean,\n): void {\n\tconst subscribe = (field.subscribe ??\n\t\t(subscription ? defaultFieldResolver : undefined)) as Resolver | undefined;\n\tif (subscribe) {\n\t\tif (!subscribe[wrapped]) field.subscribe = checking(args, where, subscribe);\n\t\treturn;\n\t}\n\tconst resolve = (field.resolve ?? defaultFieldResolver) as Resolver;\n\tif (!resolve[wrapped]) field.resolve = checking(args, where, resolve);\n}\n\n/**\n * Checks every `@constraint` of `schema` before the resolvers run. Each\n * field with a constrained argument — directly, or anywhere inside an input\n * type it takes — gets its resolver (and its `subscribe`) wrapped: the\n * arguments are parsed by the Zod schema the directives describe, the\n * resolver receives the parsed value, and an invalid input becomes one\n * `BAD_USER_INPUT` error. Fields with nothing to check are left untouched.\n *\n * The schema is built here, once: a constraint that cannot apply (a\n * `minLength` on an `Int`, an unknown format, a bad pattern) throws now, not\n * on the first request. The resolvers are wrapped in place and the same\n * schema is returned, so it goes wherever it went before; calling it twice\n * wraps nothing twice. Call it last, once every resolver is attached: a\n * resolver set on a field afterwards replaces the check.\n *\n * A `@constraint` on an interface's argument must be repeated on each\n * object's field, which is the one that resolves; a field that drops it\n * fails here.\n *\n * The directives are read from the SDL (`astNode`), so a schema built from\n * type definitions — `buildSchema`, `makeExecutableSchema`, Yoga, Apollo —\n * is checked; a code-first schema with no SDL has no `@constraint` to read.\n *\n * `options.formats` adds the application's own formats; wrapping a schema\n * again throws unless the record has the same names, each the very same\n * schema (no formats after some, or some after none, throws too), since its\n * fields are already checked against the first ones.\n */\nexport function withValidation<\n\tS extends GraphQLSchema,\n\t// biome-ignore lint/complexity/noBannedTypes: no formats of its own by default\n\tconst F extends FormatSchemas = {},\n>(schema: S, options: WithValidationOptions<F> = {}): S {\n\tconst formats: FormatSchemas = options.formats ?? {};\n\tconst holder = schema as unknown as { [wrappedWith]?: FormatSchemas };\n\tconst before = holder[wrappedWith];\n\tif (before && !sameFormats(before, formats)) {\n\t\tthrow new Error(\n\t\t\t'withValidation: this schema is already wrapped with other formats. Call withValidation once, with every format.',\n\t\t);\n\t}\n\tconst directive = checkConstraints(\n\t\tschema,\n\t\t(type, field, args) => {\n\t\t\tconst subscription = type === schema.getSubscriptionType();\n\t\t\twrap(field, args, `${type.name}.${field.name}`, subscription);\n\t\t},\n\t\tformats,\n\t);\n\tif (!directive) {\n\t\tthrow new Error(\n\t\t\t'withValidation: this schema declares no @constraint directive. Add constraintTypeDefs to its type definitions.',\n\t\t);\n\t}\n\tif (!before) Object.defineProperty(schema, wrappedWith, { value: formats });\n\treturn schema;\n}\n\n/** The same names, each the very same schema. */\nfunction sameFormats(a: FormatSchemas, b: FormatSchemas): boolean {\n\tconst names = Object.keys(a);\n\treturn (\n\t\tnames.length === Object.keys(b).length &&\n\t\tnames.every((name) => Object.hasOwn(b, name) && a[name] === b[name])\n\t);\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": ";;;;;;;;;AAAA;AA8BA,SAAS,QAAQ,CAChB,OACA,cACoB;AAAA,EACpB,OAAO,MAAM,OAAO,IAAI,CAAC,UAAU;AAAA,IAClC,MAAM,OAAO,MAAM,KAAK,IAAI,CAAC,QAC5B,OAAO,QAAQ,WAAW,MAAM,OAAO,GAAG,CAC3C;AAAA,IACA,MAAM,aAAa,eAAe,KAAK;AAAA,IACvC,OAAO,aACJ,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,MAAM,MAAM,WAAW,IAC7D,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,MAAM,KAAK;AAAA,GACpD;AAAA;AAGF,SAAS,OAAO,CAAC,OAAe,QAAyC;AAAA,EACxE,OAAO,SAAS;AAAA,EAChB,MAAM,SAAS,QACZ,GAAG,MAAM,KAAK,KAAK,GAAG,KAAK,gBAAgB,MAAM,YACjD;AAAA,EACH,MAAM,aAAqC,EAAE,MAAM,kBAAkB,OAAO;AAAA,EAC5E,OAAO,IAAI,aAAa,yBAAyB,UAAU,UAAU;AAAA,IACpE;AAAA,EACD,CAAC;AAAA;AAUK,SAAS,YAAY,CAAC,OAAe,OAAiC;AAAA,EAC5E,OAAO,QAAQ,OAAO,SAAS,KAAK,CAAC;AAAA;AAItC,eAAsB,SAAS,CAC9B,QACA,MACA,OACA,cACmB;AAAA,EACnB,MAAM,SAAS,MAAM,OAAO,eAAe,IAAI;AAAA,EAC/C,IAAI,CAAC,OAAO;AAAA,IACX,MAAM,QAAQ,OAAO,SAAS,OAAO,OAAO,YAAY,CAAC;AAAA,EAC1D,OAAO,OAAO;AAAA;;AC5Ef;AAyBO,SAAS,SAKf,CACA,QACA,SAWC;AAAA,EAED,MAAM,UACL,UAAU,SAAS,SAAS,EAAE,OAAO,MAAuB;AAAA,EAE7D,OAAO,OAAO,QAAQ,MAAM,SAAS,SAAoC;AAAA,IACxE,MAAM,QAAQ,GAAG,KAAK,WAAW,QAAQ,KAAK;AAAA,IAC9C,MAAM,SAAU,MAAM,UAAU,SAAS,MAAM,KAAK;AAAA,IAGpD,OAAO,MAAM,QAAQ,QAAQ,QAAQ,SAAS,IAAI;AAAA;AAAA;;ACtDpD;AAAA;AAAA;
|
|
10
|
-
"debugId": "
|
|
9
|
+
"mappings": ";;;;;;;;;AAAA;AA8BA,SAAS,QAAQ,CAChB,OACA,cACoB;AAAA,EACpB,OAAO,MAAM,OAAO,IAAI,CAAC,UAAU;AAAA,IAClC,MAAM,OAAO,MAAM,KAAK,IAAI,CAAC,QAC5B,OAAO,QAAQ,WAAW,MAAM,OAAO,GAAG,CAC3C;AAAA,IACA,MAAM,aAAa,eAAe,KAAK;AAAA,IACvC,OAAO,aACJ,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,MAAM,MAAM,WAAW,IAC7D,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,MAAM,KAAK;AAAA,GACpD;AAAA;AAGF,SAAS,OAAO,CAAC,OAAe,QAAyC;AAAA,EACxE,OAAO,SAAS;AAAA,EAChB,MAAM,SAAS,QACZ,GAAG,MAAM,KAAK,KAAK,GAAG,KAAK,gBAAgB,MAAM,YACjD;AAAA,EACH,MAAM,aAAqC,EAAE,MAAM,kBAAkB,OAAO;AAAA,EAC5E,OAAO,IAAI,aAAa,yBAAyB,UAAU,UAAU;AAAA,IACpE;AAAA,EACD,CAAC;AAAA;AAUK,SAAS,YAAY,CAAC,OAAe,OAAiC;AAAA,EAC5E,OAAO,QAAQ,OAAO,SAAS,KAAK,CAAC;AAAA;AAItC,eAAsB,SAAS,CAC9B,QACA,MACA,OACA,cACmB;AAAA,EACnB,MAAM,SAAS,MAAM,OAAO,eAAe,IAAI;AAAA,EAC/C,IAAI,CAAC,OAAO;AAAA,IACX,MAAM,QAAQ,OAAO,SAAS,OAAO,OAAO,YAAY,CAAC;AAAA,EAC1D,OAAO,OAAO;AAAA;;AC5Ef;AAyBO,SAAS,SAKf,CACA,QACA,SAWC;AAAA,EAED,MAAM,UACL,UAAU,SAAS,SAAS,EAAE,OAAO,MAAuB;AAAA,EAE7D,OAAO,OAAO,QAAQ,MAAM,SAAS,SAAoC;AAAA,IACxE,MAAM,QAAQ,GAAG,KAAK,WAAW,QAAQ,KAAK;AAAA,IAC9C,MAAM,SAAU,MAAM,UAAU,SAAS,MAAM,KAAK;AAAA,IAGpD,OAAO,MAAM,QAAQ,QAAQ,QAAQ,SAAS,IAAI;AAAA;AAAA;;ACtDpD;AAAA;AAAA;AAaA,IAAM,UAAU,OAAO,IAAI,kCAAkC;AAE7D,IAAM,cAAc,OAAO,IAAI,kCAAkC;AAiBjE,SAAS,QAAQ,CAChB,QACA,OACA,SACW;AAAA,EACX,MAAM,QAAkB,OAAO,QAAQ,MAAM,SAAS,SACrD,MAAM,QACL,QACA,MAAM,UAAU,QAAQ,MAAM,OAAO,YAAY,GACjD,SACA,IACD;AAAA,EACD,MAAM,WAAW;AAAA,EACjB,OAAO;AAAA;AASR,SAAS,IAAI,CACZ,OACA,MACA,OACA,cACO;AAAA,EACP,MAAM,YAAa,MAAM,cACvB,eAAe,uBAAuB;AAAA,EACxC,IAAI,WAAW;AAAA,IACd,IAAI,CAAC,UAAU;AAAA,MAAU,MAAM,YAAY,SAAS,MAAM,OAAO,SAAS;AAAA,IAC1E;AAAA,EACD;AAAA,EACA,MAAM,UAAW,MAAM,WAAW;AAAA,EAClC,IAAI,CAAC,QAAQ;AAAA,IAAU,MAAM,UAAU,SAAS,MAAM,OAAO,OAAO;AAAA;AA+B9D,SAAS,cAIf,CAAC,QAAW,UAAoC,CAAC,GAAM;AAAA,EACvD,MAAM,UAAyB,QAAQ,WAAW,CAAC;AAAA,EACnD,MAAM,SAAS;AAAA,EACf,MAAM,SAAS,OAAO;AAAA,EACtB,IAAI,UAAU,CAAC,YAAY,QAAQ,OAAO,GAAG;AAAA,IAC5C,MAAM,IAAI,MACT,iHACD;AAAA,EACD;AAAA,EACA,MAAM,YAAY,kBACjB,QACA,CAAC,MAAM,OAAO,SAAS;AAAA,IACtB,MAAM,eAAe,SAAS,OAAO,oBAAoB;AAAA,IACzD,KAAK,OAAO,MAAM,GAAG,KAAK,QAAQ,MAAM,QAAQ,YAAY;AAAA,KAE7D,OACD;AAAA,EACA,IAAI,CAAC,WAAW;AAAA,IACf,MAAM,IAAI,MACT,gHACD;AAAA,EACD;AAAA,EACA,IAAI,CAAC;AAAA,IAAQ,OAAO,eAAe,QAAQ,aAAa,EAAE,OAAO,QAAQ,CAAC;AAAA,EAC1E,OAAO;AAAA;AAIR,SAAS,WAAW,CAAC,GAAkB,GAA2B;AAAA,EACjE,MAAM,QAAQ,OAAO,KAAK,CAAC;AAAA,EAC3B,OACC,MAAM,WAAW,OAAO,KAAK,CAAC,EAAE,UAChC,MAAM,MAAM,CAAC,SAAS,OAAO,OAAO,GAAG,IAAI,KAAK,EAAE,UAAU,EAAE,KAAK;AAAA;",
|
|
10
|
+
"debugId": "B588BD3EF63841CA64756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
package/dist/rules/all.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
export * from './contains';
|
|
2
|
-
export * from './ends-with';
|
|
3
|
-
export * from './exclusive-max';
|
|
4
|
-
export * from './exclusive-min';
|
|
5
|
-
export * from './format';
|
|
6
|
-
export * from './max';
|
|
7
|
-
export * from './max-items';
|
|
8
|
-
export * from './max-length';
|
|
9
|
-
export * from './min';
|
|
10
|
-
export * from './min-items';
|
|
11
|
-
export * from './min-length';
|
|
12
|
-
export * from './multiple-of';
|
|
13
|
-
export * from './not-contains';
|
|
14
|
-
export * from './pattern';
|
|
15
|
-
export * from './starts-with';
|
|
1
|
+
export * from './contains.js';
|
|
2
|
+
export * from './ends-with.js';
|
|
3
|
+
export * from './exclusive-max.js';
|
|
4
|
+
export * from './exclusive-min.js';
|
|
5
|
+
export * from './format.js';
|
|
6
|
+
export * from './max.js';
|
|
7
|
+
export * from './max-items.js';
|
|
8
|
+
export * from './max-length.js';
|
|
9
|
+
export * from './min.js';
|
|
10
|
+
export * from './min-items.js';
|
|
11
|
+
export * from './min-length.js';
|
|
12
|
+
export * from './multiple-of.js';
|
|
13
|
+
export * from './not-contains.js';
|
|
14
|
+
export * from './pattern.js';
|
|
15
|
+
export * from './starts-with.js';
|
|
16
16
|
//# sourceMappingURL=all.d.ts.map
|
package/dist/rules/contains.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const containsRule: import("./rule").Rule<"contains", "String", "string">;
|
|
1
|
+
export declare const containsRule: import("./rule.js").Rule<"contains", "String", "string">;
|
|
2
2
|
//# sourceMappingURL=contains.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const endsWithRule: import("./rule").Rule<"endsWith", "String", "string">;
|
|
1
|
+
export declare const endsWithRule: import("./rule.js").Rule<"endsWith", "String", "string">;
|
|
2
2
|
//# sourceMappingURL=ends-with.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const exclusiveMaxRule: import("./rule").Rule<"exclusiveMax", "Float", "number">;
|
|
1
|
+
export declare const exclusiveMaxRule: import("./rule.js").Rule<"exclusiveMax", "Float", "number">;
|
|
2
2
|
//# sourceMappingURL=exclusive-max.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const exclusiveMinRule: import("./rule").Rule<"exclusiveMin", "Float", "number">;
|
|
1
|
+
export declare const exclusiveMinRule: import("./rule.js").Rule<"exclusiveMin", "Float", "number">;
|
|
2
2
|
//# sourceMappingURL=exclusive-min.d.ts.map
|
package/dist/rules/format.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const formatRule: import("./rule").Rule<"format", "String", "string">;
|
|
1
|
+
export declare const formatRule: import("./rule.js").Rule<"format", "String", "string">;
|
|
2
2
|
//# sourceMappingURL=format.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/rules/format.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/rules/format.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,UAAU,qDAmBrB,CAAC"}
|
package/dist/rules/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
|
-
import * as all from './all';
|
|
2
|
+
import * as all from './all.js';
|
|
3
|
+
import { type RuleContext } from './rule.js';
|
|
3
4
|
/**
|
|
4
5
|
* One of the rules, as its own type: a builder narrows it on `target`, so a
|
|
5
6
|
* number schema never reaches a string rule.
|
|
@@ -20,9 +21,13 @@ export declare const rules: { readonly [A in ConstraintArgument]: Extract<Regist
|
|
|
20
21
|
* target: the one place the rule's typed `toZod` meets a schema built from
|
|
21
22
|
* GraphQL types, which the compiler cannot relate.
|
|
22
23
|
*/
|
|
23
|
-
export declare function applyRule(rule: RegisteredRule, schema: z.ZodType, value: string | number): z.ZodType;
|
|
24
|
+
export declare function applyRule(rule: RegisteredRule, schema: z.ZodType, value: string | number, context: RuleContext): z.ZodType;
|
|
24
25
|
/** `applyRule` for source: the rule's typed `toCode` on a schema's source. */
|
|
25
|
-
export declare function applyRuleCode(rule: RegisteredRule, source: string, value: string | number): string;
|
|
26
|
-
/**
|
|
26
|
+
export declare function applyRuleCode(rule: RegisteredRule, source: string, value: string | number, context: RuleContext): string;
|
|
27
|
+
/**
|
|
28
|
+
* The `@constraint` argument whose rule refused with this issue, if any: the
|
|
29
|
+
* one the issue is marked with (an application format's), else the one rule
|
|
30
|
+
* that owns its shape.
|
|
31
|
+
*/
|
|
27
32
|
export declare function constraintOf(issue: z.core.$ZodIssue): ConstraintArgument | undefined;
|
|
28
33
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAExE;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC;AAE5D,uCAAuC;AACvC,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,KAAK,EAEb,EACJ,QAAQ,EAAE,CAAC,IAAI,kBAAkB,GAAG,OAAO,CAAC,cAAc,EAAE;IAAE,QAAQ,EAAE,CAAC,CAAA;CAAE,CAAC,GAC5E,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,CAAC,CAAC,OAAO,EACjB,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,OAAO,EAAE,WAAW,GAClB,CAAC,CAAC,OAAO,CAOX;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAC5B,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,OAAO,EAAE,WAAW,GAClB,MAAM,CAOR;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,GACrB,kBAAkB,GAAG,SAAS,CAMhC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const maxItemsRule: import("./rule").Rule<"maxItems", "Int", "list">;
|
|
1
|
+
export declare const maxItemsRule: import("./rule.js").Rule<"maxItems", "Int", "list">;
|
|
2
2
|
//# sourceMappingURL=max-items.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const maxLengthRule: import("./rule").Rule<"maxLength", "Int", "string">;
|
|
1
|
+
export declare const maxLengthRule: import("./rule.js").Rule<"maxLength", "Int", "string">;
|
|
2
2
|
//# sourceMappingURL=max-length.d.ts.map
|
package/dist/rules/max.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const maxRule: import("./rule").Rule<"max", "Float", "number">;
|
|
1
|
+
export declare const maxRule: import("./rule.js").Rule<"max", "Float", "number">;
|
|
2
2
|
//# sourceMappingURL=max.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const minItemsRule: import("./rule").Rule<"minItems", "Int", "list">;
|
|
1
|
+
export declare const minItemsRule: import("./rule.js").Rule<"minItems", "Int", "list">;
|
|
2
2
|
//# sourceMappingURL=min-items.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const minLengthRule: import("./rule").Rule<"minLength", "Int", "string">;
|
|
1
|
+
export declare const minLengthRule: import("./rule.js").Rule<"minLength", "Int", "string">;
|
|
2
2
|
//# sourceMappingURL=min-length.d.ts.map
|
package/dist/rules/min.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const minRule: import("./rule").Rule<"min", "Float", "number">;
|
|
1
|
+
export declare const minRule: import("./rule.js").Rule<"min", "Float", "number">;
|
|
2
2
|
//# sourceMappingURL=min.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const multipleOfRule: import("./rule").Rule<"multipleOf", "Float", "number">;
|
|
1
|
+
export declare const multipleOfRule: import("./rule.js").Rule<"multipleOf", "Float", "number">;
|
|
2
2
|
//# sourceMappingURL=multiple-of.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const notContainsRule: import("./rule").Rule<"notContains", "String", "string">;
|
|
1
|
+
export declare const notContainsRule: import("./rule.js").Rule<"notContains", "String", "string">;
|
|
2
2
|
//# sourceMappingURL=not-contains.d.ts.map
|
package/dist/rules/pattern.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const patternRule: import("./rule").Rule<"pattern", "String", "string">;
|
|
1
|
+
export declare const patternRule: import("./rule.js").Rule<"pattern", "String", "string">;
|
|
2
2
|
//# sourceMappingURL=pattern.d.ts.map
|
package/dist/rules/rule.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
|
-
import type { StringSchema } from '../formats/format';
|
|
2
|
+
import type { StringSchema } from '../formats/format.js';
|
|
3
|
+
import type { FormatRegistry } from '../formats/registry.js';
|
|
3
4
|
/** The schema each kind of rule narrows. */
|
|
4
5
|
export interface Targets {
|
|
5
6
|
string: StringSchema;
|
|
@@ -7,6 +8,15 @@ export interface Targets {
|
|
|
7
8
|
list: z.ZodArray<z.ZodType>;
|
|
8
9
|
}
|
|
9
10
|
export type Target = keyof Targets;
|
|
11
|
+
/**
|
|
12
|
+
* What a rule may read besides its value: the formats `format` resolves
|
|
13
|
+
* against, the built-in ones and the application's own, and, when writing
|
|
14
|
+
* source, how to write one of the application's (`InputCodeOptions.format`).
|
|
15
|
+
*/
|
|
16
|
+
export interface RuleContext {
|
|
17
|
+
readonly formats: FormatRegistry;
|
|
18
|
+
readonly formatCode?: ((name: string) => string) | undefined;
|
|
19
|
+
}
|
|
10
20
|
/** What a `@constraint` argument's value can be. */
|
|
11
21
|
interface ValueOf {
|
|
12
22
|
Int: number;
|
|
@@ -27,12 +37,12 @@ export interface Rule<A extends string = string, T extends keyof ValueOf = keyof
|
|
|
27
37
|
readonly type: T;
|
|
28
38
|
readonly target: K;
|
|
29
39
|
readonly base?: true;
|
|
30
|
-
readonly toZod: (schema: Targets[K], value: ValueOf[T]) => Targets[K];
|
|
40
|
+
readonly toZod: (schema: Targets[K], value: ValueOf[T], context: RuleContext) => Targets[K];
|
|
31
41
|
/**
|
|
32
42
|
* `schema` is the source of the schema this rule narrows. The source names
|
|
33
43
|
* zod as a free `z`: the generated file imports it.
|
|
34
44
|
*/
|
|
35
|
-
readonly toCode: (schema: string, value: ValueOf[T]) => string;
|
|
45
|
+
readonly toCode: (schema: string, value: ValueOf[T], context: RuleContext) => string;
|
|
36
46
|
/**
|
|
37
47
|
* Whether a Zod issue is this rule's refusal, so the error a client gets
|
|
38
48
|
* names the rule (`constraint: 'minLength'`), which does not change with
|
|
@@ -46,7 +56,14 @@ export interface IssueFields {
|
|
|
46
56
|
readonly origin?: string;
|
|
47
57
|
readonly format?: string;
|
|
48
58
|
readonly inclusive?: boolean;
|
|
59
|
+
readonly params?: Readonly<Record<string, unknown>>;
|
|
49
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* The `params` key that marks an issue as one argument's whatever its code:
|
|
63
|
+
* an application format's issues carry `format` there, so a `.regex()` or a
|
|
64
|
+
* `.refine()` inside one is not read as `pattern` or `notContains`.
|
|
65
|
+
*/
|
|
66
|
+
export declare const RULE_PARAM = "nxgtConstraint";
|
|
50
67
|
/** Declares a rule, with its argument name kept literal. */
|
|
51
68
|
export declare function defineRule<const A extends string, T extends keyof ValueOf, K extends Target>(rule: Rule<A, T, K>): Rule<A, T, K>;
|
|
52
69
|
/** A string literal as source: quotes and escapes included. */
|
package/dist/rules/rule.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../../src/rules/rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../../src/rules/rule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,4CAA4C;AAC5C,MAAM,WAAW,OAAO;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;CAC5B;AAED,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;AAEnC;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;CAC7D;AAED,oDAAoD;AACpD,UAAU,OAAO;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,IAAI,CACpB,CAAC,SAAS,MAAM,GAAG,MAAM,EACzB,CAAC,SAAS,MAAM,OAAO,GAAG,MAAM,OAAO,EACvC,CAAC,SAAS,MAAM,GAAG,MAAM;IAEzB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,CACf,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAClB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,EACjB,OAAO,EAAE,WAAW,KAChB,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,CAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,EACjB,OAAO,EAAE,WAAW,KAChB,MAAM,CAAC;IACZ;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC;CAC/C;AAED,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACpD;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,mBAAmB,CAAC;AAE3C,4DAA4D;AAC5D,wBAAgB,UAAU,CACzB,KAAK,CAAC,CAAC,SAAS,MAAM,EACtB,CAAC,SAAS,MAAM,OAAO,EACvB,CAAC,SAAS,MAAM,EACf,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAEpC;AAED,+DAA+D;AAC/D,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAA+B,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const startsWithRule: import("./rule").Rule<"startsWith", "String", "string">;
|
|
1
|
+
export declare const startsWithRule: import("./rule.js").Rule<"startsWith", "String", "string">;
|
|
2
2
|
//# sourceMappingURL=starts-with.d.ts.map
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { type GraphQLSchema } from 'graphql';
|
|
2
|
+
import type { FormatSchemas, OwnFormats } from './formats/registry.js';
|
|
3
|
+
/** What `withValidation` takes besides the schema. */
|
|
4
|
+
export interface WithValidationOptions<F extends FormatSchemas = FormatSchemas> {
|
|
5
|
+
/**
|
|
6
|
+
* The application's own formats, which `@constraint(format: "...")` may
|
|
7
|
+
* name beside the built-in ones: one Zod string schema each, keyed by its
|
|
8
|
+
* name (lowercase letters, digits and hyphens). A built-in name is a type
|
|
9
|
+
* error, and refused at startup.
|
|
10
|
+
*/
|
|
11
|
+
readonly formats?: OwnFormats<F>;
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* Checks every `@constraint` of `schema` before the resolvers run. Each
|
|
4
15
|
* field with a constrained argument — directly, or anywhere inside an input
|
|
@@ -21,6 +32,11 @@ import { type GraphQLSchema } from 'graphql';
|
|
|
21
32
|
* The directives are read from the SDL (`astNode`), so a schema built from
|
|
22
33
|
* type definitions — `buildSchema`, `makeExecutableSchema`, Yoga, Apollo —
|
|
23
34
|
* is checked; a code-first schema with no SDL has no `@constraint` to read.
|
|
35
|
+
*
|
|
36
|
+
* `options.formats` adds the application's own formats; wrapping a schema
|
|
37
|
+
* again throws unless the record has the same names, each the very same
|
|
38
|
+
* schema (no formats after some, or some after none, throws too), since its
|
|
39
|
+
* fields are already checked against the first ones.
|
|
24
40
|
*/
|
|
25
|
-
export declare function withValidation<S extends GraphQLSchema>(schema: S): S;
|
|
41
|
+
export declare function withValidation<S extends GraphQLSchema, const F extends FormatSchemas = {}>(schema: S, options?: WithValidationOptions<F>): S;
|
|
26
42
|
//# sourceMappingURL=with-validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-validation.d.ts","sourceRoot":"","sources":["../src/with-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,KAAK,aAAa,EAClB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"with-validation.d.ts","sourceRoot":"","sources":["../src/with-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,KAAK,aAAa,EAClB,MAAM,SAAS,CAAC;AAIjB,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAQpE,sDAAsD;AACtD,MAAM,WAAW,qBAAqB,CACrC,CAAC,SAAS,aAAa,GAAG,aAAa;IAEvC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;CACjC;AA0CD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,cAAc,CAC7B,CAAC,SAAS,aAAa,EAEvB,KAAK,CAAC,CAAC,SAAS,aAAa,GAAG,EAAE,EACjC,MAAM,EAAE,CAAC,EAAE,OAAO,GAAE,qBAAqB,CAAC,CAAC,CAAM,GAAG,CAAC,CAwBtD"}
|
package/docs/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| Page | Read it when |
|
|
6
6
|
| --- | --- |
|
|
7
|
-
| [Constraints](guide/constraints.md) | you write `@constraint` in your schema: setup with `withValidation`, every argument and its Zod equivalent, the formats, lists and nested inputs |
|
|
7
|
+
| [Constraints](guide/constraints.md) | you write `@constraint` in your schema: setup with `withValidation`, every argument and its Zod equivalent, the formats and your own, lists and nested inputs |
|
|
8
8
|
| [Errors](guide/errors.md) | you handle the `BAD_USER_INPUT` error in a client, build it yourself with `badUserInput`, or add rules a directive cannot say with `validated` |
|
|
9
9
|
|
|
10
10
|
## Reference
|