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.
Files changed (44) hide show
  1. package/README.md +128 -16
  2. package/dist/server/index.d.ts +1 -1
  3. package/dist/server/nexus/plugins/field-authorize.plugin.d.ts +21 -0
  4. package/dist/server/nexus/plugins/field-authorize.plugin.js +68 -35
  5. package/dist/server/nexus/plugins/middleware.plugin.d.ts +10 -2
  6. package/dist/server/nexus/plugins/middleware.plugin.js +20 -33
  7. package/dist/server/services/file-service.d.ts +7 -6
  8. package/dist/server/services/file-service.js +36 -36
  9. package/dist/server/services/index.d.ts +1 -1
  10. package/dist/server/types.d.ts +18 -3
  11. package/dist/server/utils/collection-type.d.ts +13 -2
  12. package/dist/server/utils/collection-type.js +13 -2
  13. package/dist/server/utils/field-builder/builder.d.ts +13 -4
  14. package/dist/server/utils/field-builder/builder.js +161 -119
  15. package/dist/server/utils/field-builder/helpers.d.ts +21 -6
  16. package/dist/server/utils/field-builder/helpers.js +26 -4
  17. package/dist/server/utils/field-builder/types.d.ts +287 -76
  18. package/dist/server/utils/graphql-extension.d.ts +24 -3
  19. package/dist/server/utils/graphql-extension.js +73 -7
  20. package/dist/server/utils/index.d.ts +2 -0
  21. package/dist/server/utils/index.js +11 -0
  22. package/dist/server/utils/loaders.d.ts +94 -0
  23. package/dist/server/utils/loaders.js +166 -0
  24. package/dist/server/utils/misc.d.ts +6 -0
  25. package/dist/server/utils/misc.js +6 -0
  26. package/dist/server/utils/override.d.ts +32 -0
  27. package/dist/server/utils/override.js +54 -26
  28. package/dist/server/utils/registry/field-protection.d.ts +43 -0
  29. package/dist/server/utils/registry/field-protection.js +65 -0
  30. package/dist/server/utils/registry/global-registry.d.ts +58 -14
  31. package/dist/server/utils/registry/global-registry.js +42 -7
  32. package/dist/server/utils/registry/index.d.ts +2 -0
  33. package/dist/server/utils/registry/index.js +2 -0
  34. package/dist/server/utils/registry/resolve-field.d.ts +1 -0
  35. package/dist/server/utils/registry/resolve-field.js +67 -0
  36. package/dist/server/utils/shadow-crud.d.ts +28 -1
  37. package/dist/server/utils/shadow-crud.js +32 -1
  38. package/dist/server/utils/single-type.d.ts +30 -1
  39. package/dist/server/utils/single-type.js +25 -0
  40. package/dist/server/utils/strapi-compat.d.ts +85 -0
  41. package/dist/server/utils/strapi-compat.js +194 -0
  42. package/dist/server/utils/types.d.ts +25 -1
  43. package/dist/tsconfig.server.tsbuildinfo +1 -1
  44. 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
- * Parse argument definitions into Nexus-compatible args.
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 if the user is not authenticated.
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
- * Combine multiple field builders into a single GraphQL extension.
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 one or more presets to a group of fields.
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 { ZodSchema, ZodTypeAny } from 'zod';
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
- * Options for configuring a GraphQL field extension.
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 FieldExtensionOptions<Type = any, TArgs = any, TOutput = Type> = {
11
- rootType: string;
12
- fieldName?: string;
13
- description?: string;
14
- deprecationReason?: string;
15
- init?: () => void;
16
- resolver?: ResolverFunction<Partial<Type> | null, TArgs, Partial<TOutput> | null>;
17
- resolverConfig: ResolverConfigOption<Partial<Type> | null, TArgs, Partial<TOutput> | null>;
18
- typeDefs?: string;
19
- types?: (() => unknown[]) | unknown[];
20
- args?: Record<string, string | unknown>;
21
- validateArgsSchema?: ZodSchema<any> | ZodTypeAny;
22
- outputType?: string | unknown;
23
- auth?: boolean;
24
- tags?: string[];
25
- traceHandlers?: Array<(info: {
26
- type: string;
27
- fieldName: string;
28
- durationMs: number;
29
- args: any;
30
- context: any;
31
- result: any;
32
- error?: any;
33
- }) => void>;
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
- * Fluent builder for extending a root field in the GraphQL schema.
37
- * All methods return a new builder instance (immutable).
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 root type to extend (e.g., 'Query', 'Mutation').
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: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
135
+ rootType<N extends string>(type: N): FieldExtensionBuilder<ParentOf<N, Type>, TArgs, TOutput>;
44
136
  /**
45
- * Set the name of the field to add/extend.
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
- * Add a description for this field (for schema docs and IDE hints).
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 this field as deprecated with a reason.
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
- * Register a function to be called when the extension is initialized.
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 function for the field.
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<Partial<Type> | null, TArgs, Partial<TOutput> | null | any>): FieldExtensionBuilder<Type, TArgs, TOutput>;
194
+ resolver(fn: ResolverFunction<FieldParent<Type>, TArgs, Partial<TOutput> | null | any>): FieldExtensionBuilder<Type, TArgs, TOutput>;
64
195
  /**
65
- * Set resolver config (policies, middlewares, etc).
66
- * Only supported for Query and Mutation root types.
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
- * Add a middleware function for the field.
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<Partial<Type> | null, TArgs>): FieldExtensionBuilder<Type, TArgs, TOutput>;
215
+ middleware(middleware: Middleware<FieldParent<Type>, TArgs>): FieldExtensionBuilder<Type, TArgs, TOutput>;
73
216
  /**
74
- * Add SDL type definitions for the field.
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 additional Nexus types if needed.
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 for the field.
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 using a Zod schema.
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: ZodSchema<TArgs> | ZodTypeAny): FieldExtensionBuilder<Type, TArgs, TOutput>;
247
+ validateArgs<S extends ZodTypeAny>(schema: S): FieldExtensionBuilder<Type, 0 extends (1 & TArgs) ? output<S> : TArgs, TOutput>;
89
248
  /**
90
- * Set the output type for the field.
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
- * Make authentication mandatory/optional for the field.
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 additional authorization logic to the field.
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<Partial<Type> | null, TArgs, Partial<TOutput> | null | any>): FieldExtensionBuilder<Type, TArgs, TOutput>;
269
+ authorize(authorizeFn: ResolverFunction<FieldParent<Type>, TArgs, Partial<TOutput> | null | any>): FieldExtensionBuilder<Type, TArgs, TOutput>;
101
270
  /**
102
- * Add a tag to this field.
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 multiple tags to this field.
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 or extension to this builder.
111
- * A preset is a function that takes a builder and returns a builder.
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
- * Attach a hook to this field.
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, TArgs>): FieldExtensionBuilder<Type, TArgs, TOutput>;
294
+ hook(hook: GraphQLFieldHook<FieldParent<Type>, TArgs>): FieldExtensionBuilder<Type, TArgs, TOutput>;
118
295
  /**
119
- * Validate the builder configuration. Throws if invalid.
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
- * Describe the current builder configuration.
303
+ * Snapshot of the configuration, for debugging and documentation tooling. The shape is not stable.
124
304
  */
125
305
  describe(): unknown;
126
306
  /**
127
- * Test the field's resolver (and optionally middleware/auth) in isolation.
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?: Partial<Type> | null;
131
- args?: TArgs;
132
- context?: ResolveContext;
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
- * Add a trace/profiling handler for this field.
139
- * Multiple handlers can be registered.
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?: (info: {
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 registration.
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
- afterResolve?: (result: any, parent: TArgs, args: TArgs, context: ResolveContext, info: GraphQLResolveInfo) => void | Promise<void>;
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
- * Overloaded createGraphQLExtension:
25
- * - When called with no arguments, returns a new builder.
26
- * - When called with a factory function returns a GraphQLExtension.
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 {};