buro26-strapi-graphql 0.0.26 → 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 +458 -439
- package/dist/server/index.d.ts +14 -8
- package/dist/server/index.js +1 -0
- package/dist/server/nexus/index.d.ts +1 -0
- package/dist/server/nexus/index.js +17 -0
- package/dist/server/nexus/plugins/field-authorize.plugin.d.ts +36 -0
- package/dist/server/nexus/plugins/field-authorize.plugin.js +122 -0
- package/dist/server/nexus/plugins/index.d.ts +2 -0
- package/dist/server/nexus/plugins/index.js +18 -0
- package/dist/server/nexus/plugins/middleware.plugin.d.ts +25 -0
- package/dist/server/nexus/plugins/middleware.plugin.js +63 -0
- package/dist/server/register.js +6 -7
- package/dist/server/services/file-service.d.ts +7 -6
- package/dist/server/services/file-service.js +36 -30
- package/dist/server/services/index.d.ts +1 -1
- package/dist/server/types.d.ts +28 -13
- 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 +33 -0
- package/dist/server/utils/field-builder/builder.js +552 -0
- package/dist/server/utils/field-builder/helpers.d.ts +34 -0
- package/dist/server/utils/field-builder/helpers.js +78 -0
- package/dist/server/utils/field-builder/index.d.ts +3 -0
- package/dist/server/utils/field-builder/index.js +19 -0
- package/dist/server/utils/field-builder/types.d.ts +371 -0
- package/dist/server/utils/field-builder/types.js +2 -0
- package/dist/server/utils/graphql-extension.d.ts +29 -3
- package/dist/server/utils/graphql-extension.js +94 -26
- package/dist/server/utils/index.d.ts +3 -1
- package/dist/server/utils/index.js +12 -1
- 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 +34 -1
- 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 +63 -0
- package/dist/server/utils/registry/global-registry.js +56 -0
- package/dist/server/utils/registry/index.d.ts +3 -0
- package/dist/server/utils/registry/index.js +19 -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 +7 -4
- package/dist/server/utils/field-builder.d.ts +0 -31
- package/dist/server/utils/field-builder.js +0 -113
- package/dist/server/utils/middleware.d.ts +0 -17
- package/dist/server/utils/middleware.js +0 -77
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createFieldGroup = exports.registerFields = exports.createGraphQLFieldPreset = exports.makeAuthMiddleware = exports.parseArgs = void 0;
|
|
4
|
+
const nexus_1 = require("nexus");
|
|
5
|
+
const utils_1 = require("@strapi/utils");
|
|
6
|
+
/**
|
|
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()`.
|
|
11
|
+
*/
|
|
12
|
+
function parseArgs(args) {
|
|
13
|
+
const nexusArgs = {};
|
|
14
|
+
for (const [argName, argType] of Object.entries(args)) {
|
|
15
|
+
if (typeof argType !== 'string') {
|
|
16
|
+
nexusArgs[argName] = argType;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const isNonNull = argType.endsWith('!');
|
|
20
|
+
const baseType = isNonNull ? argType.slice(0, -1) : argType;
|
|
21
|
+
nexusArgs[argName] = isNonNull
|
|
22
|
+
? (0, nexus_1.nonNull)((0, nexus_1.arg)({ type: baseType }))
|
|
23
|
+
: (0, nexus_1.arg)({ type: baseType });
|
|
24
|
+
}
|
|
25
|
+
return nexusArgs;
|
|
26
|
+
}
|
|
27
|
+
exports.parseArgs = parseArgs;
|
|
28
|
+
/**
|
|
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.
|
|
32
|
+
*/
|
|
33
|
+
function makeAuthMiddleware() {
|
|
34
|
+
return (next) => async (root, args, context, info) => {
|
|
35
|
+
var _a;
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
if (!((_a = context.state) === null || _a === void 0 ? void 0 : _a.user)) {
|
|
38
|
+
throw new utils_1.errors.ForbiddenError('Not authenticated');
|
|
39
|
+
}
|
|
40
|
+
return next(root, args, context, info);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
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
|
+
*/
|
|
54
|
+
function createGraphQLFieldPreset(preset) {
|
|
55
|
+
return preset;
|
|
56
|
+
}
|
|
57
|
+
exports.createGraphQLFieldPreset = createGraphQLFieldPreset;
|
|
58
|
+
/**
|
|
59
|
+
* Export several fields from one file. Builds each builder and returns the extensions.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* export default registerFields(fieldA, fieldB)
|
|
63
|
+
*/
|
|
64
|
+
function registerFields(...fields) {
|
|
65
|
+
return fields.map(builder => builder.build());
|
|
66
|
+
}
|
|
67
|
+
exports.registerFields = registerFields;
|
|
68
|
+
/**
|
|
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))
|
|
74
|
+
*/
|
|
75
|
+
function createFieldGroup(fields, ...presets) {
|
|
76
|
+
return fields.map(field => presets.reduce((acc, preset) => preset(acc), field));
|
|
77
|
+
}
|
|
78
|
+
exports.createFieldGroup = createFieldGroup;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./helpers"), exports);
|
|
19
|
+
__exportStar(require("./builder"), exports);
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import type { GraphQLResolveInfo } from 'graphql';
|
|
2
|
+
import type { output, ZodTypeAny } from 'zod';
|
|
3
|
+
import type { ResolverConfigOption, ResolverFunction } from '../../types';
|
|
4
|
+
import type { Middleware } from '../../nexus';
|
|
5
|
+
import type { createGraphQLExtension } from '../graphql-extension';
|
|
6
|
+
import type { ParentOf, ResolveContext } from '../types';
|
|
7
|
+
/**
|
|
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`.
|
|
14
|
+
*/
|
|
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;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
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
|
|
124
|
+
*/
|
|
125
|
+
export interface FieldExtensionBuilder<Type = any, TArgs = any, TOutput = Type> {
|
|
126
|
+
/**
|
|
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')`).
|
|
134
|
+
*/
|
|
135
|
+
rootType<N extends string>(type: N): FieldExtensionBuilder<ParentOf<N, Type>, TArgs, TOutput>;
|
|
136
|
+
/**
|
|
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()`).
|
|
171
|
+
*/
|
|
172
|
+
fieldName(name: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
173
|
+
/**
|
|
174
|
+
* Description shown in the GraphQL schema, GraphiQL and IDE hints.
|
|
175
|
+
*/
|
|
176
|
+
description(text: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
177
|
+
/**
|
|
178
|
+
* Mark the field as deprecated in the schema.
|
|
179
|
+
*
|
|
180
|
+
* @param reason - Shown to API consumers; mention the replacement.
|
|
181
|
+
*/
|
|
182
|
+
deprecate(reason: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
183
|
+
/**
|
|
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.
|
|
186
|
+
*/
|
|
187
|
+
init(fn: () => void): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
188
|
+
/**
|
|
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.
|
|
193
|
+
*/
|
|
194
|
+
resolver(fn: ResolverFunction<FieldParent<Type>, TArgs, Partial<TOutput> | null | any>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
195
|
+
/**
|
|
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
|
|
203
|
+
*/
|
|
204
|
+
resolverConfig(config: (() => ResolverConfigOption<Type, TArgs, TOutput>) | ResolverConfigOption<Type, TArgs, TOutput>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
205
|
+
/**
|
|
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
|
+
* })
|
|
214
|
+
*/
|
|
215
|
+
middleware(middleware: Middleware<FieldParent<Type>, TArgs>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
216
|
+
/**
|
|
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.
|
|
219
|
+
*/
|
|
220
|
+
typeDefs(sdl: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
221
|
+
/**
|
|
222
|
+
* Add Nexus types needed by this field (object types, input types, enums). Appended to earlier calls.
|
|
223
|
+
*/
|
|
224
|
+
types(typesFn: (() => unknown[]) | unknown[]): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
225
|
+
/**
|
|
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()`.
|
|
233
|
+
*/
|
|
234
|
+
args(a: Record<string, string | unknown>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
235
|
+
/**
|
|
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
|
|
246
|
+
*/
|
|
247
|
+
validateArgs<S extends ZodTypeAny>(schema: S): FieldExtensionBuilder<Type, 0 extends (1 & TArgs) ? output<S> : TArgs, TOutput>;
|
|
248
|
+
/**
|
|
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}.
|
|
251
|
+
*/
|
|
252
|
+
outputType(type: string | unknown): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
253
|
+
/**
|
|
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
|
|
259
|
+
*/
|
|
260
|
+
withAuth(auth: boolean): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
261
|
+
/**
|
|
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)
|
|
268
|
+
*/
|
|
269
|
+
authorize(authorizeFn: ResolverFunction<FieldParent<Type>, TArgs, Partial<TOutput> | null | any>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
270
|
+
/**
|
|
271
|
+
* Add a tag. Tags let global middleware/authorization target groups of fields.
|
|
272
|
+
*
|
|
273
|
+
* @see {@link registerGlobalAuthorize}
|
|
274
|
+
* @see {@link registerGlobalMiddleware}
|
|
275
|
+
*/
|
|
276
|
+
tag(tag: string): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
277
|
+
/**
|
|
278
|
+
* Add several tags at once.
|
|
279
|
+
*
|
|
280
|
+
* @see {@link FieldExtensionBuilder.tag}
|
|
281
|
+
*/
|
|
282
|
+
tags(tags: string[]): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
283
|
+
/**
|
|
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}
|
|
288
|
+
*/
|
|
289
|
+
use(preset: (builder: FieldExtensionBuilder) => FieldExtensionBuilder<Type, TArgs, TOutput>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
290
|
+
/**
|
|
291
|
+
* Add `beforeResolve` / `afterResolve` hooks. They run inside the middlewares, around the resolver;
|
|
292
|
+
* hooks are awaited and can throw to abort the request.
|
|
293
|
+
*/
|
|
294
|
+
hook(hook: GraphQLFieldHook<FieldParent<Type>, TArgs>): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
295
|
+
/**
|
|
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`.
|
|
300
|
+
*/
|
|
301
|
+
validate(): void;
|
|
302
|
+
/**
|
|
303
|
+
* Snapshot of the configuration, for debugging and documentation tooling. The shape is not stable.
|
|
304
|
+
*/
|
|
305
|
+
describe(): unknown;
|
|
306
|
+
/**
|
|
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')
|
|
316
|
+
*/
|
|
317
|
+
test(params: {
|
|
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` */
|
|
324
|
+
info?: GraphQLResolveInfo;
|
|
325
|
+
/**
|
|
326
|
+
* Skip all middlewares (global and field-level).
|
|
327
|
+
* @default false
|
|
328
|
+
*/
|
|
329
|
+
skipMiddleware?: boolean;
|
|
330
|
+
/**
|
|
331
|
+
* Skip authentication and all authorization (global and field-level).
|
|
332
|
+
* @default false
|
|
333
|
+
*/
|
|
334
|
+
skipAuth?: boolean;
|
|
335
|
+
}): Promise<any>;
|
|
336
|
+
/**
|
|
337
|
+
* Log or collect timing information for each resolve. Can be called multiple times.
|
|
338
|
+
*
|
|
339
|
+
* @param handler - Defaults to a coloured console table.
|
|
340
|
+
*/
|
|
341
|
+
trace(handler?: FieldTraceHandler): FieldExtensionBuilder<Type, TArgs, TOutput>;
|
|
342
|
+
/**
|
|
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.
|
|
347
|
+
*/
|
|
348
|
+
build(): ReturnType<typeof createGraphQLExtension>;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* A reusable function that configures a field builder.
|
|
352
|
+
*
|
|
353
|
+
* @see {@link createGraphQLFieldPreset}
|
|
354
|
+
*/
|
|
355
|
+
export type GraphQLFieldPreset = (builder: FieldExtensionBuilder) => FieldExtensionBuilder;
|
|
356
|
+
/**
|
|
357
|
+
* Hooks around a field resolver. Both are awaited; throwing aborts the resolve.
|
|
358
|
+
*/
|
|
359
|
+
export type GraphQLFieldHook<TParent = any, TArgs = any> = {
|
|
360
|
+
/** Runs after argument validation, before the resolver */
|
|
361
|
+
beforeResolve?: (parent: TParent, 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>;
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* A preset applied to every field of a group.
|
|
367
|
+
*
|
|
368
|
+
* @see {@link createFieldGroup}
|
|
369
|
+
*/
|
|
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>;
|
|
@@ -13,13 +14,38 @@ interface GraphQLExtensionBuilder {
|
|
|
13
14
|
plugins(pluginsFn: (() => any[]) | any[]): GraphQLExtensionBuilder;
|
|
14
15
|
resolvers(resolversObj: (() => ResolversObject) | ResolversObject): GraphQLExtensionBuilder;
|
|
15
16
|
resolversConfig(config: (() => Record<string, ResolverConfigOption>) | Record<string, ResolverConfigOption>): GraphQLExtensionBuilder;
|
|
17
|
+
/**
|
|
18
|
+
* Apply a preset or extension to this builder.
|
|
19
|
+
* A preset is a function that takes a builder and returns a builder.
|
|
20
|
+
*/
|
|
21
|
+
use(preset: (builder: GraphQLExtensionBuilder) => GraphQLExtensionBuilder): GraphQLExtensionBuilder;
|
|
16
22
|
build(): GraphQLExtension;
|
|
17
23
|
}
|
|
18
24
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
+
* }))
|
|
22
39
|
*/
|
|
23
40
|
export declare function createGraphQLExtension(): GraphQLExtensionBuilder;
|
|
24
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;
|
|
25
51
|
export {};
|
|
@@ -1,48 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createGraphQLExtension = void 0;
|
|
3
|
+
exports.mapFieldPresetToExtension = exports.createGraphQLExtension = void 0;
|
|
4
|
+
const field_builder_1 = require("./field-builder");
|
|
4
5
|
/**
|
|
5
6
|
* Internal function that creates a new builder instance.
|
|
6
7
|
*/
|
|
7
|
-
function graphQLExtensionBuilder() {
|
|
8
|
-
// Options to store configuration.
|
|
9
|
-
const options = {};
|
|
8
|
+
function graphQLExtensionBuilder(options = {}) {
|
|
10
9
|
return {
|
|
11
10
|
init(fn) {
|
|
12
|
-
options
|
|
13
|
-
return this;
|
|
11
|
+
return graphQLExtensionBuilder({ ...options, init: fn });
|
|
14
12
|
},
|
|
15
13
|
typeDefs(sdl) {
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
return graphQLExtensionBuilder({
|
|
15
|
+
...options,
|
|
16
|
+
typeDefs: typeof sdl === 'string' ? () => sdl : sdl
|
|
17
|
+
});
|
|
18
18
|
},
|
|
19
19
|
types(typesFn) {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
return graphQLExtensionBuilder({
|
|
21
|
+
...options,
|
|
22
|
+
types: typeof typesFn === 'function' ? typesFn : () => typesFn
|
|
23
|
+
});
|
|
22
24
|
},
|
|
23
25
|
plugins(pluginsFn) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
return graphQLExtensionBuilder({
|
|
27
|
+
...options,
|
|
28
|
+
plugins: typeof pluginsFn === 'function' ? pluginsFn : () => pluginsFn
|
|
29
|
+
});
|
|
26
30
|
},
|
|
27
31
|
resolvers(resolversObj) {
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
return graphQLExtensionBuilder({
|
|
33
|
+
...options,
|
|
34
|
+
resolvers: typeof resolversObj === 'function' ? resolversObj : () => resolversObj
|
|
35
|
+
});
|
|
30
36
|
},
|
|
31
37
|
resolversConfig(configObj) {
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
return graphQLExtensionBuilder({
|
|
39
|
+
...options,
|
|
40
|
+
resolversConfig: typeof configObj === 'function' ? configObj : () => configObj
|
|
41
|
+
});
|
|
34
42
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
init: options.init,
|
|
39
|
-
typeDefs: options.typeDefs,
|
|
40
|
-
types: options.types,
|
|
41
|
-
plugins: options.plugins,
|
|
42
|
-
resolvers: options.resolvers,
|
|
43
|
-
resolversConfig: options.resolversConfig,
|
|
44
|
-
};
|
|
43
|
+
use(preset) {
|
|
44
|
+
return preset(this);
|
|
45
45
|
},
|
|
46
|
+
build() {
|
|
47
|
+
return { ...options };
|
|
48
|
+
}
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
51
|
function createGraphQLExtension(extension) {
|
|
@@ -53,3 +56,68 @@ function createGraphQLExtension(extension) {
|
|
|
53
56
|
return extension(strapi);
|
|
54
57
|
}
|
|
55
58
|
exports.createGraphQLExtension = createGraphQLExtension;
|
|
59
|
+
/**
|
|
60
|
+
* Maps a field-level preset to all (or matching) fields in a GraphQLExtension.
|
|
61
|
+
* Throws if a field is not a function (i.e., not a resolver).
|
|
62
|
+
*
|
|
63
|
+
* @param fieldPreset - a GraphQLFieldPreset (FieldExtensionBuilder => FieldExtensionBuilder)
|
|
64
|
+
* @param matcher - a function (type, fieldName) => boolean or an array of field names
|
|
65
|
+
* @returns a function that takes a GraphQLExtension and returns a new one with wrapped resolvers
|
|
66
|
+
*/
|
|
67
|
+
function mapFieldPresetToExtension(fieldPreset, matcher = () => true) {
|
|
68
|
+
return (extension) => {
|
|
69
|
+
var _a, _b, _c;
|
|
70
|
+
const originalResolvers = ((_a = extension.resolvers) === null || _a === void 0 ? void 0 : _a.call(extension)) || {};
|
|
71
|
+
const newResolvers = {};
|
|
72
|
+
for (const type of Object.keys(originalResolvers)) {
|
|
73
|
+
newResolvers[type] = {};
|
|
74
|
+
// @ts-ignore
|
|
75
|
+
for (const fieldName of Object.keys(originalResolvers[type] || {})) {
|
|
76
|
+
const shouldApply = typeof matcher === 'function'
|
|
77
|
+
? matcher(type, fieldName)
|
|
78
|
+
: matcher.includes(fieldName);
|
|
79
|
+
// @ts-ignore
|
|
80
|
+
const originalResolver = originalResolvers[type][fieldName];
|
|
81
|
+
if (typeof originalResolver !== 'function') {
|
|
82
|
+
throw new Error(`Cannot apply field preset: ${type}.${fieldName} is not a resolver function`);
|
|
83
|
+
}
|
|
84
|
+
if (shouldApply) {
|
|
85
|
+
// Build a field builder for this resolver
|
|
86
|
+
let fieldBuilder = (0, field_builder_1.createGraphQLFieldBuilder)(type)
|
|
87
|
+
.fieldName(fieldName)
|
|
88
|
+
.resolver(originalResolver);
|
|
89
|
+
// Apply the preset
|
|
90
|
+
fieldBuilder = fieldPreset(fieldBuilder);
|
|
91
|
+
// Build the field and extract the resolver
|
|
92
|
+
const builtExtension = fieldBuilder.build();
|
|
93
|
+
const builtType = (_c = (_b = builtExtension === null || builtExtension === void 0 ? void 0 : builtExtension.types) === null || _b === void 0 ? void 0 : _b.call(builtExtension)) === null || _c === void 0 ? void 0 : _c.find((t) => t.name === type);
|
|
94
|
+
if (!builtType || !builtType.definition) {
|
|
95
|
+
throw new Error(`Cannot extract wrapped resolver for ${type}.${fieldName}`);
|
|
96
|
+
}
|
|
97
|
+
// Extract the resolver from the field definition
|
|
98
|
+
let wrappedResolver = null;
|
|
99
|
+
builtType.definition({
|
|
100
|
+
field: (name, config) => {
|
|
101
|
+
if (name === fieldName) {
|
|
102
|
+
wrappedResolver = config.resolve;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
if (typeof wrappedResolver !== 'function') {
|
|
107
|
+
throw new Error(`Preset did not produce a valid resolver for ${type}.${fieldName}`);
|
|
108
|
+
}
|
|
109
|
+
newResolvers[type][fieldName] = wrappedResolver;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
newResolvers[type][fieldName] = originalResolver;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Return a new extension with the updated resolvers
|
|
117
|
+
return {
|
|
118
|
+
...extension,
|
|
119
|
+
resolvers: () => newResolvers,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
exports.mapFieldPresetToExtension = mapFieldPresetToExtension;
|