buro26-strapi-graphql 0.1.0 → 0.2.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 +128 -16
- package/dist/server/index.d.ts +1 -1
- package/dist/server/nexus/plugins/field-authorize.plugin.d.ts +21 -0
- package/dist/server/nexus/plugins/field-authorize.plugin.js +68 -35
- package/dist/server/nexus/plugins/middleware.plugin.d.ts +10 -2
- package/dist/server/nexus/plugins/middleware.plugin.js +20 -33
- package/dist/server/services/file-service.d.ts +7 -6
- package/dist/server/services/file-service.js +36 -36
- package/dist/server/services/index.d.ts +1 -1
- package/dist/server/types.d.ts +18 -3
- package/dist/server/utils/collection-type.d.ts +13 -2
- package/dist/server/utils/collection-type.js +13 -2
- package/dist/server/utils/field-builder/builder.d.ts +13 -4
- package/dist/server/utils/field-builder/builder.js +161 -119
- package/dist/server/utils/field-builder/helpers.d.ts +21 -6
- package/dist/server/utils/field-builder/helpers.js +26 -4
- package/dist/server/utils/field-builder/types.d.ts +287 -76
- package/dist/server/utils/graphql-extension.d.ts +24 -3
- package/dist/server/utils/graphql-extension.js +73 -7
- package/dist/server/utils/index.d.ts +2 -0
- package/dist/server/utils/index.js +11 -0
- package/dist/server/utils/loaders.d.ts +94 -0
- package/dist/server/utils/loaders.js +166 -0
- package/dist/server/utils/misc.d.ts +6 -0
- package/dist/server/utils/misc.js +6 -0
- package/dist/server/utils/override.d.ts +32 -0
- package/dist/server/utils/override.js +54 -26
- package/dist/server/utils/registry/field-protection.d.ts +43 -0
- package/dist/server/utils/registry/field-protection.js +65 -0
- package/dist/server/utils/registry/global-registry.d.ts +58 -14
- package/dist/server/utils/registry/global-registry.js +42 -7
- package/dist/server/utils/registry/index.d.ts +2 -0
- package/dist/server/utils/registry/index.js +2 -0
- package/dist/server/utils/registry/resolve-field.d.ts +1 -0
- package/dist/server/utils/registry/resolve-field.js +67 -0
- package/dist/server/utils/shadow-crud.d.ts +28 -1
- package/dist/server/utils/shadow-crud.js +32 -1
- package/dist/server/utils/single-type.d.ts +30 -1
- package/dist/server/utils/single-type.js +25 -0
- package/dist/server/utils/strapi-compat.d.ts +85 -0
- package/dist/server/utils/strapi-compat.js +194 -0
- package/dist/server/utils/types.d.ts +25 -1
- package/dist/tsconfig.server.tsbuildinfo +1 -1
- package/package.json +3 -2
|
@@ -4,7 +4,10 @@ exports.createFieldGroup = exports.registerFields = exports.createGraphQLFieldPr
|
|
|
4
4
|
const nexus_1 = require("nexus");
|
|
5
5
|
const utils_1 = require("@strapi/utils");
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Convert an args map with type strings (`'String!'`, `'[ID]'`) into Nexus args.
|
|
8
|
+
* Non-string values (e.g. `arg({ type: 'X' })`) are passed through unchanged.
|
|
9
|
+
*
|
|
10
|
+
* @internal Used by `.args()`.
|
|
8
11
|
*/
|
|
9
12
|
function parseArgs(args) {
|
|
10
13
|
const nexusArgs = {};
|
|
@@ -23,7 +26,9 @@ function parseArgs(args) {
|
|
|
23
26
|
}
|
|
24
27
|
exports.parseArgs = parseArgs;
|
|
25
28
|
/**
|
|
26
|
-
* Middleware that throws
|
|
29
|
+
* Middleware that throws a `ForbiddenError` ("Not authenticated") when there is no `ctx.state.user`.
|
|
30
|
+
*
|
|
31
|
+
* @deprecated Use `.withAuth(true)` on the field builder; it is checked before authorization runs.
|
|
27
32
|
*/
|
|
28
33
|
function makeAuthMiddleware() {
|
|
29
34
|
return (next) => async (root, args, context, info) => {
|
|
@@ -36,19 +41,36 @@ function makeAuthMiddleware() {
|
|
|
36
41
|
};
|
|
37
42
|
}
|
|
38
43
|
exports.makeAuthMiddleware = makeAuthMiddleware;
|
|
44
|
+
/**
|
|
45
|
+
* Define a reusable preset for `.use()`. Identity function that exists for typing and readability.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* export const adminOnly = createGraphQLFieldPreset((builder) =>
|
|
49
|
+
* builder.withAuth(true).authorize((_p, _a, ctx) => ctx.state.user?.role?.type === 'admin')
|
|
50
|
+
* )
|
|
51
|
+
*
|
|
52
|
+
* createGraphQLFieldBuilder().use(adminOnly).rootType('Query') // ...
|
|
53
|
+
*/
|
|
39
54
|
function createGraphQLFieldPreset(preset) {
|
|
40
55
|
return preset;
|
|
41
56
|
}
|
|
42
57
|
exports.createGraphQLFieldPreset = createGraphQLFieldPreset;
|
|
43
58
|
/**
|
|
44
|
-
*
|
|
59
|
+
* Export several fields from one file. Builds each builder and returns the extensions.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* export default registerFields(fieldA, fieldB)
|
|
45
63
|
*/
|
|
46
64
|
function registerFields(...fields) {
|
|
47
65
|
return fields.map(builder => builder.build());
|
|
48
66
|
}
|
|
49
67
|
exports.registerFields = registerFields;
|
|
50
68
|
/**
|
|
51
|
-
* Apply
|
|
69
|
+
* Apply presets to every field in a group (e.g. one auth rule for all fields of a feature).
|
|
70
|
+
* Presets are applied in order. Returns unbuilt builders; export them with {@link registerFields}.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* export default registerFields(...createFieldGroup([fieldA, fieldB], requireUser))
|
|
52
74
|
*/
|
|
53
75
|
function createFieldGroup(fields, ...presets) {
|
|
54
76
|
return fields.map(field => presets.reduce((acc, preset) => preset(acc), field));
|
|
@@ -1,160 +1,371 @@
|
|
|
1
1
|
import type { GraphQLResolveInfo } from 'graphql';
|
|
2
|
-
import type {
|
|
2
|
+
import type { output, ZodTypeAny } from 'zod';
|
|
3
3
|
import type { ResolverConfigOption, ResolverFunction } from '../../types';
|
|
4
4
|
import type { Middleware } from '../../nexus';
|
|
5
5
|
import type { createGraphQLExtension } from '../graphql-extension';
|
|
6
|
-
import type { ResolveContext } from '../types';
|
|
6
|
+
import type { ParentOf, ResolveContext } from '../types';
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* The `parent` value resolvers, authorize functions and middlewares receive.
|
|
9
|
+
*
|
|
10
|
+
* - Untyped builders (`Type` is `any`, e.g. `Query`/`Mutation` fields): `any`.
|
|
11
|
+
* - Entity types with an `id` (e.g. registered via {@link Register} `types`): `Partial<Type>` with `id`
|
|
12
|
+
* required. Strapi always passes the entity, but relations are only present when populated.
|
|
13
|
+
* - Other types: `Partial<Type> | null`.
|
|
9
14
|
*/
|
|
10
|
-
export type
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
export type FieldParent<Type> = 0 extends (1 & Type) ? any : Type extends {
|
|
16
|
+
id: infer Id;
|
|
17
|
+
} ? Partial<Type> & {
|
|
18
|
+
id: Id;
|
|
19
|
+
} : Partial<Type> | null;
|
|
20
|
+
/**
|
|
21
|
+
* TypeScript types for GraphQL scalar names used in `.args()` type strings.
|
|
22
|
+
* Unknown names (custom scalars, input types) map to `unknown`.
|
|
23
|
+
*/
|
|
24
|
+
export type GraphQLScalarTypes = {
|
|
25
|
+
String: string;
|
|
26
|
+
ID: string | number;
|
|
27
|
+
Int: number;
|
|
28
|
+
Float: number;
|
|
29
|
+
Long: number;
|
|
30
|
+
Boolean: boolean;
|
|
31
|
+
Date: string;
|
|
32
|
+
DateTime: string;
|
|
33
|
+
Time: string;
|
|
34
|
+
JSON: unknown;
|
|
35
|
+
};
|
|
36
|
+
type ParseNonNullArg<S extends string> = S extends `[${infer Item}]` ? Array<ParseArgType<Item>> : S extends keyof GraphQLScalarTypes ? GraphQLScalarTypes[S] : unknown;
|
|
37
|
+
/** TypeScript type for a GraphQL type string such as `'String!'`, `'[ID!]'` or `'Int'`. */
|
|
38
|
+
export type ParseArgType<S extends string> = S extends `${infer Inner}!` ? ParseNonNullArg<Inner> : ParseNonNullArg<S> | null;
|
|
39
|
+
type RequiredArgKeys<A> = {
|
|
40
|
+
[K in keyof A]: A[K] extends `${string}!` ? K : never;
|
|
41
|
+
}[keyof A];
|
|
42
|
+
/**
|
|
43
|
+
* Argument types for an `.args()` map, for typing a builder explicitly. Non-null (`!`) arguments are
|
|
44
|
+
* required; others optional. Non-string definitions (e.g. Nexus `arg()`) are `unknown`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* type A = InferArgs<{ id: 'ID!'; limit: 'Int' }> // { id: string | number; limit?: number | null }
|
|
48
|
+
*/
|
|
49
|
+
export type InferArgs<A> = {
|
|
50
|
+
[K in Extract<keyof A, RequiredArgKeys<A>>]: A[K] extends string ? ParseArgType<A[K]> : unknown;
|
|
51
|
+
} & {
|
|
52
|
+
[K in Exclude<keyof A, RequiredArgKeys<A>>]?: A[K] extends string ? ParseArgType<A[K]> : unknown;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Information passed to a trace handler after a field resolved (or failed).
|
|
56
|
+
*
|
|
57
|
+
* @see {@link FieldExtensionBuilder.trace}
|
|
58
|
+
*/
|
|
59
|
+
export type FieldTraceInfo = {
|
|
60
|
+
/** GraphQL parent type name, e.g. `Query` */
|
|
61
|
+
type: string;
|
|
62
|
+
fieldName: string;
|
|
63
|
+
/** Wall-clock duration of the resolver, in milliseconds */
|
|
64
|
+
durationMs: number;
|
|
65
|
+
args: any;
|
|
66
|
+
context: any;
|
|
67
|
+
/** The resolved value; `undefined` when the resolver threw */
|
|
68
|
+
result: any;
|
|
69
|
+
/** The thrown error, if any */
|
|
70
|
+
error?: any;
|
|
34
71
|
};
|
|
35
72
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
73
|
+
* Handler that receives {@link FieldTraceInfo}. Runs asynchronously after the resolver and never affects
|
|
74
|
+
* the response; errors thrown by a handler are logged and swallowed.
|
|
75
|
+
*/
|
|
76
|
+
export type FieldTraceHandler = (info: FieldTraceInfo) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Relation configuration set by {@link FieldExtensionBuilder.relation}.
|
|
79
|
+
*/
|
|
80
|
+
export type RelationOptions = {
|
|
81
|
+
/** Content type UID that owns the relation, e.g. `api::order.order` */
|
|
82
|
+
contentType: string;
|
|
83
|
+
/** Relation attribute name on that content type, e.g. `customer` */
|
|
84
|
+
attribute: string;
|
|
85
|
+
/**
|
|
86
|
+
* Resolve a to-one relation with the per-request relation loader: one query for all parents in the
|
|
87
|
+
* request instead of one query per parent. Output is sanitized for the content API, like Strapi's own
|
|
88
|
+
* resolver does.
|
|
89
|
+
*
|
|
90
|
+
* Not supported for to-many relations, because those take filter/pagination arguments per parent.
|
|
91
|
+
*
|
|
92
|
+
* @default false
|
|
93
|
+
* @see {@link loaders}
|
|
94
|
+
*/
|
|
95
|
+
batch?: boolean;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Fluent, immutable builder for one GraphQL field. Every method returns a **new** builder, so a
|
|
99
|
+
* partially configured builder can be shared as a base for several fields.
|
|
100
|
+
*
|
|
101
|
+
* A field needs at least {@link FieldExtensionBuilder.rootType | rootType},
|
|
102
|
+
* {@link FieldExtensionBuilder.fieldName | fieldName}, {@link FieldExtensionBuilder.outputType | outputType}
|
|
103
|
+
* and {@link FieldExtensionBuilder.resolver | resolver} — or `rootType` plus
|
|
104
|
+
* {@link FieldExtensionBuilder.relation | relation}.
|
|
105
|
+
*
|
|
106
|
+
* Export the builder (or an array via `registerFields`) as the default export of a file in a `graphql`
|
|
107
|
+
* folder; the plugin calls {@link FieldExtensionBuilder.build | build} for you.
|
|
108
|
+
*
|
|
109
|
+
* @typeParam Type - Parent value the resolver receives. Inferred from {@link Register} `types` when
|
|
110
|
+
* `rootType` is a registered type name.
|
|
111
|
+
* @typeParam TArgs - Field arguments.
|
|
112
|
+
* @typeParam TOutput - Resolved value.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* export default createGraphQLFieldBuilder()
|
|
116
|
+
* .rootType('Query')
|
|
117
|
+
* .fieldName('hello')
|
|
118
|
+
* .args({ name: 'String!' })
|
|
119
|
+
* .outputType('String')
|
|
120
|
+
* .withAuth(true)
|
|
121
|
+
* .resolver((_parent, { name }) => `Hello ${name}`)
|
|
122
|
+
*
|
|
123
|
+
* @see https://gitlab.com/buro26/public/strapi/strapi-graphql-extension#-recommended-the-field-builder-pattern
|
|
38
124
|
*/
|
|
39
125
|
export interface FieldExtensionBuilder<Type = any, TArgs = any, TOutput = Type> {
|
|
40
126
|
/**
|
|
41
|
-
* Set the
|
|
127
|
+
* Set the GraphQL type the field is added to: `Query`, `Mutation` or any object type such as a
|
|
128
|
+
* content type (`Customer`).
|
|
129
|
+
*
|
|
130
|
+
* When `type` is registered in {@link Register} `types`, the resolver's `parent` is typed accordingly.
|
|
131
|
+
*
|
|
132
|
+
* @param type - GraphQL type name.
|
|
133
|
+
* @throws Error when the root type was already set (e.g. by `createGraphQLFieldBuilder('Query')`).
|
|
42
134
|
*/
|
|
43
|
-
rootType(type:
|
|
135
|
+
rootType<N extends string>(type: N): FieldExtensionBuilder<ParentOf<N, Type>, TArgs, TOutput>;
|
|
44
136
|
/**
|
|
45
|
-
*
|
|
137
|
+
* Define the field as a relation of a Strapi content type. It gets exactly the type, filter/pagination
|
|
138
|
+
* arguments, resolver (query validation, output sanitizing, draft & publish handling) and `<target>.find`
|
|
139
|
+
* permission scope of the field Strapi generates, on Strapi v4 and v5.
|
|
140
|
+
*
|
|
141
|
+
* Sets {@link FieldExtensionBuilder.fieldName | fieldName} to `attribute`; `outputType` and `resolver`
|
|
142
|
+
* are optional (a custom `resolver` replaces Strapi's association resolver). Combine with
|
|
143
|
+
* `.authorize()`, `.middleware()` or presets to protect or extend the relation.
|
|
144
|
+
*
|
|
145
|
+
* | | v4 | v5 |
|
|
146
|
+
* |---|---|---|
|
|
147
|
+
* | to-one | `XEntityResponse` | `X` |
|
|
148
|
+
* | to-many | `XRelationResponseCollection` | `[X]!` and `<attribute>_connection` |
|
|
149
|
+
*
|
|
150
|
+
* On v5 both to-many fields get the same authorization and middleware. Note that the v5 list field is
|
|
151
|
+
* non-null: when `.authorize()` denies it, GraphQL nulls the parent object (standard error propagation).
|
|
152
|
+
*
|
|
153
|
+
* @param contentType - UID of the content type that owns the relation.
|
|
154
|
+
* @param attribute - Name of a regular (non-polymorphic) relation attribute.
|
|
155
|
+
* @param options - See {@link RelationOptions}.
|
|
156
|
+
* @throws Error when a relation was already set, or `fieldName` was set to a different name.
|
|
157
|
+
* @throws ApplicationError at schema build when the attribute is not a relation, or `batch` is used on
|
|
158
|
+
* a to-many relation.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* createGraphQLFieldBuilder()
|
|
162
|
+
* .rootType('Order')
|
|
163
|
+
* .relation('api::order.order', 'customer', { batch: true })
|
|
164
|
+
* .authorize(isOrderCustomer)
|
|
165
|
+
*/
|
|
166
|
+
relation(contentType: string, attribute: string, options?: Pick<RelationOptions, 'batch'>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
167
|
+
/**
|
|
168
|
+
* Set the field name. Use the name of an existing field to replace it.
|
|
169
|
+
*
|
|
170
|
+
* @throws Error when the field name was already set (also by `.relation()`).
|
|
46
171
|
*/
|
|
47
172
|
fieldName(name: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
48
173
|
/**
|
|
49
|
-
*
|
|
174
|
+
* Description shown in the GraphQL schema, GraphiQL and IDE hints.
|
|
50
175
|
*/
|
|
51
176
|
description(text: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
52
177
|
/**
|
|
53
|
-
* Mark
|
|
178
|
+
* Mark the field as deprecated in the schema.
|
|
179
|
+
*
|
|
180
|
+
* @param reason - Shown to API consumers; mention the replacement.
|
|
54
181
|
*/
|
|
55
182
|
deprecate(reason: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
56
183
|
/**
|
|
57
|
-
*
|
|
184
|
+
* Run a function once when the plugin registers the field (Strapi's `register` phase), e.g. to
|
|
185
|
+
* configure shadow CRUD. Can be called multiple times; functions run in order.
|
|
58
186
|
*/
|
|
59
187
|
init(fn: () => void): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
60
188
|
/**
|
|
61
|
-
* Set the resolver
|
|
189
|
+
* Set the resolver. It runs inside the authorization and middlewares, after argument validation and
|
|
190
|
+
* `beforeResolve` hooks.
|
|
191
|
+
*
|
|
192
|
+
* @throws Error when a resolver was already set.
|
|
62
193
|
*/
|
|
63
|
-
resolver(fn: ResolverFunction<
|
|
194
|
+
resolver(fn: ResolverFunction<FieldParent<Type>, TArgs, Partial<TOutput> | null | any>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
64
195
|
/**
|
|
65
|
-
*
|
|
66
|
-
* Only
|
|
196
|
+
* Strapi resolver config (policies, Strapi middlewares, `auth`). Merged with earlier calls.
|
|
197
|
+
* Only applies to `Query` and `Mutation` fields; for other types use `.authorize()` / `.middleware()`.
|
|
198
|
+
*
|
|
199
|
+
* @deprecated Use {@link FieldExtensionBuilder.withAuth | withAuth} for authentication and
|
|
200
|
+
* {@link FieldExtensionBuilder.authorize | authorize} / {@link FieldExtensionBuilder.middleware | middleware}
|
|
201
|
+
* for the rest; they work on every type and run in a defined order.
|
|
202
|
+
* @see https://docs.strapi.io/dev-docs/plugins/graphql#customization
|
|
67
203
|
*/
|
|
68
|
-
resolverConfig(config: (() => ResolverConfigOption<Type, TArgs, TOutput>) | ResolverConfigOption<Type, TArgs, TOutput>): FieldExtensionBuilder
|
|
204
|
+
resolverConfig(config: (() => ResolverConfigOption<Type, TArgs, TOutput>) | ResolverConfigOption<Type, TArgs, TOutput>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
69
205
|
/**
|
|
70
|
-
*
|
|
206
|
+
* Wrap the resolver. Middlewares receive `next` and return a resolver; call `next` to continue.
|
|
207
|
+
* Field middlewares run after global middlewares and before `protectFields` middlewares.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* .middleware((next) => async (parent, args, ctx, info) => {
|
|
211
|
+
* const result = await next(parent, args, ctx, info)
|
|
212
|
+
* return result ?? fallback
|
|
213
|
+
* })
|
|
71
214
|
*/
|
|
72
|
-
middleware(middleware: Middleware<
|
|
215
|
+
middleware(middleware: Middleware<FieldParent<Type>, TArgs>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
73
216
|
/**
|
|
74
|
-
* Add SDL type definitions
|
|
217
|
+
* Add SDL type definitions needed by this field (e.g. its output type). Appended to earlier calls.
|
|
218
|
+
* Prefer {@link FieldExtensionBuilder.types | types} with Nexus for type-safe definitions.
|
|
75
219
|
*/
|
|
76
220
|
typeDefs(sdl: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
77
221
|
/**
|
|
78
|
-
* Add
|
|
222
|
+
* Add Nexus types needed by this field (object types, input types, enums). Appended to earlier calls.
|
|
79
223
|
*/
|
|
80
224
|
types(typesFn: (() => unknown[]) | unknown[]): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
81
225
|
/**
|
|
82
|
-
* Set the arguments
|
|
226
|
+
* Set the field arguments. Replaces earlier calls.
|
|
227
|
+
*
|
|
228
|
+
* This does not change the resolver's `args` type. Type them with {@link FieldExtensionBuilder.validateArgs}
|
|
229
|
+
* (inferred from the schema), or explicitly with {@link InferArgs}:
|
|
230
|
+
* `createGraphQLFieldBuilder<unknown, InferArgs<typeof args>>()`.
|
|
231
|
+
*
|
|
232
|
+
* @param a - Map of argument name to a type string (`'String!'`, `'ID'`) or a Nexus `arg()`.
|
|
83
233
|
*/
|
|
84
234
|
args(a: Record<string, string | unknown>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
85
235
|
/**
|
|
86
|
-
* Validate arguments
|
|
236
|
+
* Validate (and parse) arguments with Zod before the resolver runs. The resolver receives the parsed
|
|
237
|
+
* value. Invalid arguments throw a Strapi `ValidationError` ("Invalid arguments") with the `ZodError`
|
|
238
|
+
* as `cause`.
|
|
239
|
+
*
|
|
240
|
+
* When the builder has no explicit `TArgs`, the resolver's `args` are typed as the schema's output.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* .args({ id: 'ID!', limit: 'Int' })
|
|
244
|
+
* .validateArgs(z.object({ id: z.coerce.number(), limit: z.number().default(10) }))
|
|
245
|
+
* .resolver((_parent, { id, limit }) => ...) // id: number, limit: number
|
|
87
246
|
*/
|
|
88
|
-
validateArgs(schema:
|
|
247
|
+
validateArgs<S extends ZodTypeAny>(schema: S): FieldExtensionBuilder<Type, 0 extends (1 & TArgs) ? output<S> : TArgs, TOutput>;
|
|
89
248
|
/**
|
|
90
|
-
* Set the output type
|
|
249
|
+
* Set the GraphQL output type: a type name (`'String'`, `'CustomerEntityResponse'`) or a Nexus wrapper
|
|
250
|
+
* (`nonNull('Boolean')`, `list('Customer')`). Not needed with {@link FieldExtensionBuilder.relation | relation}.
|
|
91
251
|
*/
|
|
92
252
|
outputType(type: string | unknown): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
93
253
|
/**
|
|
94
|
-
*
|
|
254
|
+
* Require an authenticated user. Checked before any authorization runs; unauthenticated requests get a
|
|
255
|
+
* `ForbiddenError` ("Not authenticated"). For `Query`/`Mutation` fields this also sets Strapi's
|
|
256
|
+
* resolver `auth` option.
|
|
257
|
+
*
|
|
258
|
+
* @default false
|
|
95
259
|
*/
|
|
96
260
|
withAuth(auth: boolean): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
97
261
|
/**
|
|
98
|
-
* Add
|
|
262
|
+
* Add an authorization check. All checks (global, field and `protectFields`) must return `true`;
|
|
263
|
+
* return `false` for a generic "Not authorized" or an `Error` to send that error to the client.
|
|
264
|
+
* Checks run in order and stop at the first failure.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* .authorize((parent, _args, ctx) => parent?.user?.id === ctx.state.user?.id)
|
|
99
268
|
*/
|
|
100
|
-
authorize(authorizeFn: ResolverFunction<
|
|
269
|
+
authorize(authorizeFn: ResolverFunction<FieldParent<Type>, TArgs, Partial<TOutput> | null | any>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
101
270
|
/**
|
|
102
|
-
* Add a tag
|
|
271
|
+
* Add a tag. Tags let global middleware/authorization target groups of fields.
|
|
272
|
+
*
|
|
273
|
+
* @see {@link registerGlobalAuthorize}
|
|
274
|
+
* @see {@link registerGlobalMiddleware}
|
|
103
275
|
*/
|
|
104
276
|
tag(tag: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
105
277
|
/**
|
|
106
|
-
* Add
|
|
278
|
+
* Add several tags at once.
|
|
279
|
+
*
|
|
280
|
+
* @see {@link FieldExtensionBuilder.tag}
|
|
107
281
|
*/
|
|
108
282
|
tags(tags: string[]): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
109
283
|
/**
|
|
110
|
-
* Apply a preset
|
|
111
|
-
*
|
|
284
|
+
* Apply a preset: a function that takes a builder and returns a configured builder. Use presets for
|
|
285
|
+
* reusable auth, feature flags or tags.
|
|
286
|
+
*
|
|
287
|
+
* @see {@link createGraphQLFieldPreset}
|
|
112
288
|
*/
|
|
113
289
|
use(preset: (builder: FieldExtensionBuilder) => FieldExtensionBuilder<Type, TArgs, TOutput>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
114
290
|
/**
|
|
115
|
-
*
|
|
291
|
+
* Add `beforeResolve` / `afterResolve` hooks. They run inside the middlewares, around the resolver;
|
|
292
|
+
* hooks are awaited and can throw to abort the request.
|
|
116
293
|
*/
|
|
117
|
-
hook(hook: GraphQLFieldHook<Type
|
|
294
|
+
hook(hook: GraphQLFieldHook<FieldParent<Type>, TArgs>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
118
295
|
/**
|
|
119
|
-
*
|
|
296
|
+
* Check that the configuration is complete. Called by `build()` and `test()`.
|
|
297
|
+
*
|
|
298
|
+
* @throws ApplicationError when `rootType` or `fieldName` is missing, `outputType`/`resolver` is missing
|
|
299
|
+
* on a non-relation field, or resolver config policies/middlewares are used outside `Query`/`Mutation`.
|
|
120
300
|
*/
|
|
121
301
|
validate(): void;
|
|
122
302
|
/**
|
|
123
|
-
*
|
|
303
|
+
* Snapshot of the configuration, for debugging and documentation tooling. The shape is not stable.
|
|
124
304
|
*/
|
|
125
305
|
describe(): unknown;
|
|
126
306
|
/**
|
|
127
|
-
*
|
|
307
|
+
* Run the field in isolation with the same pipeline as in production: `withAuth`, global and field
|
|
308
|
+
* authorization, `protectFields` checks, middlewares, argument validation, hooks and tracing.
|
|
309
|
+
*
|
|
310
|
+
* @returns The resolved value.
|
|
311
|
+
* @throws ForbiddenError when authentication or authorization fails (unless `skipAuth`).
|
|
312
|
+
* @throws ApplicationError for relation fields without a custom resolver (they need a running Strapi).
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* await expect(field.test({ context: { state: {} } })).rejects.toThrow('Not authenticated')
|
|
128
316
|
*/
|
|
129
317
|
test(params: {
|
|
130
|
-
parent?:
|
|
131
|
-
|
|
132
|
-
|
|
318
|
+
parent?: FieldParent<Type>;
|
|
319
|
+
/** Raw arguments, as a client sends them (before `validateArgs` parsing) */
|
|
320
|
+
args?: TArgs | Record<string, unknown>;
|
|
321
|
+
/** Resolver context; set `state.user` to simulate a logged-in user */
|
|
322
|
+
context?: Partial<ResolveContext> | Record<string, unknown>;
|
|
323
|
+
/** Defaults to a minimal object with `fieldName` and `parentType.name` */
|
|
133
324
|
info?: GraphQLResolveInfo;
|
|
325
|
+
/**
|
|
326
|
+
* Skip all middlewares (global and field-level).
|
|
327
|
+
* @default false
|
|
328
|
+
*/
|
|
134
329
|
skipMiddleware?: boolean;
|
|
330
|
+
/**
|
|
331
|
+
* Skip authentication and all authorization (global and field-level).
|
|
332
|
+
* @default false
|
|
333
|
+
*/
|
|
135
334
|
skipAuth?: boolean;
|
|
136
335
|
}): Promise<any>;
|
|
137
336
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
337
|
+
* Log or collect timing information for each resolve. Can be called multiple times.
|
|
338
|
+
*
|
|
339
|
+
* @param handler - Defaults to a coloured console table.
|
|
140
340
|
*/
|
|
141
|
-
trace(handler?:
|
|
142
|
-
type: string;
|
|
143
|
-
fieldName: string;
|
|
144
|
-
durationMs: number;
|
|
145
|
-
args: any;
|
|
146
|
-
context: any;
|
|
147
|
-
result: any;
|
|
148
|
-
error?: any;
|
|
149
|
-
}) => void): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
341
|
+
trace(handler?: FieldTraceHandler): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
150
342
|
/**
|
|
151
|
-
* Build the extension for
|
|
343
|
+
* Build the GraphQL extension. Called by the plugin for default exports in `graphql` folders, so you
|
|
344
|
+
* normally don't call this yourself. A builder can only be built once.
|
|
345
|
+
*
|
|
346
|
+
* @throws ApplicationError when the builder was already built or {@link FieldExtensionBuilder.validate | validate} fails.
|
|
152
347
|
*/
|
|
153
348
|
build(): ReturnType<typeof createGraphQLExtension>;
|
|
154
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* A reusable function that configures a field builder.
|
|
352
|
+
*
|
|
353
|
+
* @see {@link createGraphQLFieldPreset}
|
|
354
|
+
*/
|
|
155
355
|
export type GraphQLFieldPreset = (builder: FieldExtensionBuilder) => FieldExtensionBuilder;
|
|
356
|
+
/**
|
|
357
|
+
* Hooks around a field resolver. Both are awaited; throwing aborts the resolve.
|
|
358
|
+
*/
|
|
156
359
|
export type GraphQLFieldHook<TParent = any, TArgs = any> = {
|
|
360
|
+
/** Runs after argument validation, before the resolver */
|
|
157
361
|
beforeResolve?: (parent: TParent, args: TArgs, context: ResolveContext, info: GraphQLResolveInfo) => void | Promise<void>;
|
|
158
|
-
|
|
362
|
+
/** Runs after the resolver succeeded, with its result */
|
|
363
|
+
afterResolve?: (result: any, parent: TParent, args: TArgs, context: ResolveContext, info: GraphQLResolveInfo) => void | Promise<void>;
|
|
159
364
|
};
|
|
365
|
+
/**
|
|
366
|
+
* A preset applied to every field of a group.
|
|
367
|
+
*
|
|
368
|
+
* @see {@link createFieldGroup}
|
|
369
|
+
*/
|
|
160
370
|
export type FieldGroupPreset<TContext = any, TArgs = any> = (builder: FieldExtensionBuilder<TContext, TArgs>) => FieldExtensionBuilder<TContext, TArgs>;
|
|
371
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GraphQLExtension, ResolverConfigOption, ResolverFunction, Strapi } from '../types';
|
|
2
|
+
import { GraphQLFieldPreset } from './field-builder';
|
|
2
3
|
type ResolversObject = {
|
|
3
4
|
Query?: Record<string, ResolverFunction>;
|
|
4
5
|
Mutation?: Record<string, ResolverFunction>;
|
|
@@ -21,10 +22,30 @@ interface GraphQLExtensionBuilder {
|
|
|
21
22
|
build(): GraphQLExtension;
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Create a full GraphQL extension: custom types, SDL, resolvers, Nexus plugins and resolver config.
|
|
26
|
+
*
|
|
27
|
+
* Use it when you need control over the whole extension, e.g. shared type bundles, full type overrides,
|
|
28
|
+
* Nexus plugins or shadow CRUD changes. For the common cases there are more focused APIs: fields with
|
|
29
|
+
* {@link createGraphQLFieldBuilder}, access to generated fields with {@link protectFields} and relations
|
|
30
|
+
* with {@link FieldExtensionBuilder.relation}.
|
|
31
|
+
*
|
|
32
|
+
* - Without arguments: returns a fluent builder (`.types()`, `.resolvers()`, ... then `.build()`).
|
|
33
|
+
* - With a factory: calls it with `strapi` and returns the {@link GraphQLExtension}.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* export default createGraphQLExtension((strapi) => ({
|
|
37
|
+
* types: () => [objectType({ name: 'Stats', definition: (t) => t.int('total') })],
|
|
38
|
+
* }))
|
|
27
39
|
*/
|
|
28
40
|
export declare function createGraphQLExtension(): GraphQLExtensionBuilder;
|
|
29
41
|
export declare function createGraphQLExtension(extension: ((strapi: Strapi) => GraphQLExtension)): GraphQLExtension;
|
|
42
|
+
/**
|
|
43
|
+
* Maps a field-level preset to all (or matching) fields in a GraphQLExtension.
|
|
44
|
+
* Throws if a field is not a function (i.e., not a resolver).
|
|
45
|
+
*
|
|
46
|
+
* @param fieldPreset - a GraphQLFieldPreset (FieldExtensionBuilder => FieldExtensionBuilder)
|
|
47
|
+
* @param matcher - a function (type, fieldName) => boolean or an array of field names
|
|
48
|
+
* @returns a function that takes a GraphQLExtension and returns a new one with wrapped resolvers
|
|
49
|
+
*/
|
|
50
|
+
export declare function mapFieldPresetToExtension(fieldPreset: GraphQLFieldPreset, matcher?: ((type: string, fieldName: string) => boolean) | string[]): (extension: GraphQLExtension) => GraphQLExtension;
|
|
30
51
|
export {};
|