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
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GraphQLExtension } from './types';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ResolveContext } from './utils';
|
|
3
|
+
import type { Middleware } from './nexus';
|
|
3
4
|
import type { GraphQLResolveInfo } from 'graphql';
|
|
4
5
|
import type { GraphQLFieldConfig } from 'graphql/type';
|
|
5
6
|
declare const _default: {
|
|
@@ -26,7 +27,7 @@ declare const _default: {
|
|
|
26
27
|
fileService: ({ strapi }: {
|
|
27
28
|
strapi: Required<import("@strapi/types").Strapi>;
|
|
28
29
|
}) => {
|
|
29
|
-
getAllFilesInGraphQlDirectory(directoryPath: string
|
|
30
|
+
getAllFilesInGraphQlDirectory(directoryPath: string): Promise<GraphQLExtension[]>;
|
|
30
31
|
};
|
|
31
32
|
graphQlService: ({ strapi }: {
|
|
32
33
|
strapi: Required<import("@strapi/types").Strapi>;
|
|
@@ -39,6 +40,7 @@ declare const _default: {
|
|
|
39
40
|
export default _default;
|
|
40
41
|
export * from './types';
|
|
41
42
|
export * from './utils';
|
|
43
|
+
export * from './nexus';
|
|
42
44
|
declare module '@strapi/strapi' {
|
|
43
45
|
interface Strapi {
|
|
44
46
|
createGraphQLExtension: (factory: (strapi: Strapi) => GraphQLExtension) => GraphQLExtension;
|
|
@@ -59,14 +61,18 @@ declare module 'nexus/dist/core' {
|
|
|
59
61
|
interface NexusOutputFieldConfig<TypeName extends string, FieldName extends string> {
|
|
60
62
|
extensions?: {
|
|
61
63
|
middlewares?: Middleware[];
|
|
64
|
+
/**
|
|
65
|
+
* A function that determines if the current user is authorized to access this field.
|
|
66
|
+
*
|
|
67
|
+
* @param parent The parent object.
|
|
68
|
+
* @param args The arguments passed to the field.
|
|
69
|
+
* @param context The context object.
|
|
70
|
+
* @param info The GraphQLResolveInfo object.
|
|
71
|
+
*/
|
|
72
|
+
authorize?: <Parent extends Object, Args extends Object, Context = ResolveContext>(parent: Parent, args: Args, context: Context, info: GraphQLResolveInfo) => boolean | Promise<boolean>;
|
|
62
73
|
} & GraphQLFieldConfig<any, any>['extensions'];
|
|
63
74
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* @param parent The parent object.
|
|
67
|
-
* @param args The arguments passed to the field.
|
|
68
|
-
* @param context The context object.
|
|
69
|
-
* @param info The GraphQLResolveInfo object.
|
|
75
|
+
* @deprecated Use `authorize` in the extensions config instead.
|
|
70
76
|
*/
|
|
71
77
|
authorize?: <Parent extends Object, Args extends Object, Context = ResolveContext>(parent: Parent, args: Args, context: Context, info: GraphQLResolveInfo) => boolean | Promise<boolean>;
|
|
72
78
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './plugins';
|
|
@@ -0,0 +1,17 @@
|
|
|
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("./plugins"), exports);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ArgsValue, GetGen, MaybePromise, SourceValue } from 'nexus/dist/typegenTypeHelpers';
|
|
2
|
+
import type { GraphQLResolveInfo } from 'graphql';
|
|
3
|
+
export type FieldAuthorizeResolver<TypeName extends string, FieldName extends string> = (root: SourceValue<TypeName>, args: ArgsValue<TypeName, FieldName>, context: GetGen<'context'>, info: GraphQLResolveInfo) => MaybePromise<boolean | Error>;
|
|
4
|
+
export interface FieldAuthorizePluginErrorConfig {
|
|
5
|
+
error: Error;
|
|
6
|
+
root: any;
|
|
7
|
+
args: any;
|
|
8
|
+
ctx: GetGen<'context'>;
|
|
9
|
+
info: GraphQLResolveInfo;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Options for {@link nexusFieldAuthorizePlugin}.
|
|
13
|
+
*/
|
|
14
|
+
export interface FieldAuthorizePluginConfig {
|
|
15
|
+
/**
|
|
16
|
+
* Turn a denied authorization into the error sent to the client.
|
|
17
|
+
* @default {@link defaultFormatError}
|
|
18
|
+
*/
|
|
19
|
+
formatError?: (authConfig: FieldAuthorizePluginErrorConfig) => Error;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Default error for denied authorization: a Strapi `ForbiddenError` with the original message and
|
|
23
|
+
* trace details (type, field, user id, args, timestamp) in `details.trace`.
|
|
24
|
+
*/
|
|
25
|
+
export declare const defaultFormatError: ({ error, root, args, ctx, info }: FieldAuthorizePluginErrorConfig) => Error;
|
|
26
|
+
/**
|
|
27
|
+
* Field-level authentication and authorization.
|
|
28
|
+
*
|
|
29
|
+
* Applies to every field in the schema (including the ones Strapi generates) and combines:
|
|
30
|
+
* `withAuth(true)` of builder fields, global authorizes, the field's own `authorize` (in `extensions` or
|
|
31
|
+
* as top-level field option) and `protectFields()` registrations.
|
|
32
|
+
*
|
|
33
|
+
* Registered automatically by this Strapi plugin; only add it yourself when building a schema outside
|
|
34
|
+
* Strapi (e.g. in tests).
|
|
35
|
+
*/
|
|
36
|
+
export declare const nexusFieldAuthorizePlugin: (authConfig?: FieldAuthorizePluginConfig) => import("nexus/dist/plugin").NexusPlugin;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nexusFieldAuthorizePlugin = exports.runAuthorization = exports.defaultFormatError = void 0;
|
|
4
|
+
const nexus_1 = require("nexus");
|
|
5
|
+
const utils_1 = require("nexus/dist/utils");
|
|
6
|
+
const utils_2 = require("@strapi/utils");
|
|
7
|
+
const graphql_1 = require("graphql");
|
|
8
|
+
const resolve_field_1 = require("../../utils/registry/resolve-field");
|
|
9
|
+
const fieldDefTypes = (0, utils_1.printedGenTyping)({
|
|
10
|
+
optional: true,
|
|
11
|
+
name: 'authorize',
|
|
12
|
+
description: `
|
|
13
|
+
Authorization for an individual field. Returning "true"
|
|
14
|
+
or "Promise<true>" means the field can be accessed.
|
|
15
|
+
Returning "false" or "Promise<false>" will respond
|
|
16
|
+
with a "Not Authorized" error for the field.
|
|
17
|
+
Returning or throwing an error will also prevent the
|
|
18
|
+
resolver from executing.
|
|
19
|
+
`,
|
|
20
|
+
type: 'FieldAuthorizeResolver<TypeName, FieldName>',
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Default error for denied authorization: a Strapi `ForbiddenError` with the original message and
|
|
24
|
+
* trace details (type, field, user id, args, timestamp) in `details.trace`.
|
|
25
|
+
*/
|
|
26
|
+
const defaultFormatError = ({ error, root, args, ctx, info }) => {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
// Add rich tracing info
|
|
29
|
+
const trace = {
|
|
30
|
+
type: info.parentType.name,
|
|
31
|
+
field: info.fieldName,
|
|
32
|
+
user: (_b = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.state) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.id,
|
|
33
|
+
args,
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
originalError: error
|
|
36
|
+
};
|
|
37
|
+
// Use Strapi's ForbiddenError and attach trace info
|
|
38
|
+
return new utils_2.errors.ForbiddenError((error === null || error === void 0 ? void 0 : error.message) || 'Not authorized', { trace });
|
|
39
|
+
};
|
|
40
|
+
exports.defaultFormatError = defaultFormatError;
|
|
41
|
+
/**
|
|
42
|
+
* Run the authentication + authorization part of a field pipeline.
|
|
43
|
+
* Resolves when allowed, rejects with the (formatted) error otherwise.
|
|
44
|
+
*
|
|
45
|
+
* @internal Used by the authorize plugin and `.test()`.
|
|
46
|
+
*/
|
|
47
|
+
const runAuthorization = async (pipeline, root, args, ctx, info, formatError = exports.defaultFormatError) => {
|
|
48
|
+
var _a, _b;
|
|
49
|
+
const fail = (error) => {
|
|
50
|
+
const finalErr = formatError({ error, root, args, ctx, info });
|
|
51
|
+
if (finalErr instanceof Error) {
|
|
52
|
+
throw finalErr;
|
|
53
|
+
}
|
|
54
|
+
console.error(`Non-Error value ${finalErr} returned from custom formatError in authorize plugin`);
|
|
55
|
+
throw new utils_2.errors.ForbiddenError('Not authorized');
|
|
56
|
+
};
|
|
57
|
+
if (pipeline.requireAuth && !((_a = ctx === null || ctx === void 0 ? void 0 : ctx.state) === null || _a === void 0 ? void 0 : _a.user)) {
|
|
58
|
+
// Same error as before, but now evaluated before any authorize function runs
|
|
59
|
+
throw new utils_2.errors.ForbiddenError('Not authenticated');
|
|
60
|
+
}
|
|
61
|
+
for (const authorize of pipeline.authorizes) {
|
|
62
|
+
let authResult;
|
|
63
|
+
try {
|
|
64
|
+
authResult = await authorize(root, args, ctx, info);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
fail(e instanceof Error ? e : new utils_2.errors.ForbiddenError(String(e)));
|
|
68
|
+
}
|
|
69
|
+
if (authResult === true)
|
|
70
|
+
continue;
|
|
71
|
+
if (authResult instanceof Error)
|
|
72
|
+
fail(authResult);
|
|
73
|
+
if (authResult === false)
|
|
74
|
+
fail(new utils_2.errors.ForbiddenError('Not authorized'));
|
|
75
|
+
fail(new utils_2.errors.ForbiddenError(`Authorize for ${(_b = info === null || info === void 0 ? void 0 : info.parentType) === null || _b === void 0 ? void 0 : _b.name}.${info === null || info === void 0 ? void 0 : info.fieldName} Expected a boolean or Error, saw ${authResult}`));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
exports.runAuthorization = runAuthorization;
|
|
79
|
+
/**
|
|
80
|
+
* Field-level authentication and authorization.
|
|
81
|
+
*
|
|
82
|
+
* Applies to every field in the schema (including the ones Strapi generates) and combines:
|
|
83
|
+
* `withAuth(true)` of builder fields, global authorizes, the field's own `authorize` (in `extensions` or
|
|
84
|
+
* as top-level field option) and `protectFields()` registrations.
|
|
85
|
+
*
|
|
86
|
+
* Registered automatically by this Strapi plugin; only add it yourself when building a schema outside
|
|
87
|
+
* Strapi (e.g. in tests).
|
|
88
|
+
*/
|
|
89
|
+
const nexusFieldAuthorizePlugin = (authConfig = {}) => {
|
|
90
|
+
const { formatError = exports.defaultFormatError } = authConfig;
|
|
91
|
+
return (0, nexus_1.plugin)({
|
|
92
|
+
name: 'CustomFieldAuthorize',
|
|
93
|
+
description: 'Custom field-level authorization for Strapi GraphQL, with tracing.',
|
|
94
|
+
fieldDefTypes: fieldDefTypes,
|
|
95
|
+
onCreateFieldResolver(config) {
|
|
96
|
+
var _a, _b, _c;
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
const extensions = config.fieldConfig.extensions;
|
|
99
|
+
if ((extensions === null || extensions === void 0 ? void 0 : extensions.authorize) != null && typeof extensions.authorize !== 'function') {
|
|
100
|
+
console.error(new Error(`The authorize property provided to ${config.fieldConfig.name} with type ${config.fieldConfig.type} should be a function, saw ${typeof extensions.authorize}`));
|
|
101
|
+
// Fail closed: a broken authorize must not silently disable the other checks on this field
|
|
102
|
+
return async function () {
|
|
103
|
+
throw new utils_2.errors.ForbiddenError('Not authorized');
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const pipeline = (0, resolve_field_1.resolveFieldPipeline)(config.parentTypeConfig.name, config.fieldConfig.name, extensions);
|
|
107
|
+
if (!pipeline.requireAuth && pipeline.authorizes.length === 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (pipeline.isProtected && (0, graphql_1.isNonNullType)(config.fieldConfig.type)) {
|
|
111
|
+
const warn = (_c = (_b = (_a = globalThis.strapi) === null || _a === void 0 ? void 0 : _a.log) === null || _b === void 0 ? void 0 : _b.warn) !== null && _c !== void 0 ? _c : console.warn;
|
|
112
|
+
warn(`[buro26-strapi-graphql] protectFields: "${config.parentTypeConfig.name}.${config.fieldConfig.name}" is non-null; ` +
|
|
113
|
+
`when access is denied GraphQL nulls the parent object instead of only this field.`);
|
|
114
|
+
}
|
|
115
|
+
return async function (root, args, ctx, info, next) {
|
|
116
|
+
await (0, exports.runAuthorization)(pipeline, root, args, ctx, info, formatError);
|
|
117
|
+
return next(root, args, ctx, info);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
exports.nexusFieldAuthorizePlugin = nexusFieldAuthorizePlugin;
|
|
@@ -0,0 +1,18 @@
|
|
|
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("./field-authorize.plugin"), exports);
|
|
18
|
+
__exportStar(require("./middleware.plugin"), exports);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { GraphQLResolveInfo } from 'graphql';
|
|
2
|
+
import type { ResolverFunction } from '../../types';
|
|
3
|
+
import type { ResolveContext } from '../../utils';
|
|
4
|
+
/**
|
|
5
|
+
* The Resolver type represents a GraphQL resolver function.
|
|
6
|
+
*/
|
|
7
|
+
export type Resolver = <Parent extends any, Args extends any, Context = ResolveContext>(parent: Parent, args: Args, context: Context, info: GraphQLResolveInfo) => any | Promise<any>;
|
|
8
|
+
/**
|
|
9
|
+
* Middleware is a function that takes a resolver and returns a new resolver.
|
|
10
|
+
*/
|
|
11
|
+
export type Middleware<TParent = any, TArgs = any> = (resolver: ResolverFunction<TParent, TArgs>) => ResolverFunction<TParent, TArgs>;
|
|
12
|
+
/**
|
|
13
|
+
* Field-level middlewares: global middlewares, the field's own `middlewares` (in `extensions` or as
|
|
14
|
+
* top-level field option) and `protectFields()` middlewares, outermost first.
|
|
15
|
+
*
|
|
16
|
+
* Registered automatically by this Strapi plugin; only add it yourself when building a schema outside
|
|
17
|
+
* Strapi (e.g. in tests).
|
|
18
|
+
*/
|
|
19
|
+
export declare const nexusMiddlewarePlugin: () => import("nexus/dist/plugin").NexusPlugin;
|
|
20
|
+
/**
|
|
21
|
+
* Example middleware that logs every resolve, including the result.
|
|
22
|
+
*
|
|
23
|
+
* Development only: it logs resolved values, which may contain personal data.
|
|
24
|
+
*/
|
|
25
|
+
export declare const loggingMiddleware: Middleware;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loggingMiddleware = exports.nexusMiddlewarePlugin = void 0;
|
|
4
|
+
const nexus_1 = require("nexus");
|
|
5
|
+
const utils_1 = require("nexus/dist/utils");
|
|
6
|
+
const resolve_field_1 = require("../../utils/registry/resolve-field");
|
|
7
|
+
const fieldDefTypes = (0, utils_1.printedGenTyping)({
|
|
8
|
+
optional: true,
|
|
9
|
+
name: 'middlewares',
|
|
10
|
+
description: `
|
|
11
|
+
Middlewares for an individual field. Each middleware receives the
|
|
12
|
+
next resolver and returns a new resolver.
|
|
13
|
+
`,
|
|
14
|
+
type: 'any'
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Field-level middlewares: global middlewares, the field's own `middlewares` (in `extensions` or as
|
|
18
|
+
* top-level field option) and `protectFields()` middlewares, outermost first.
|
|
19
|
+
*
|
|
20
|
+
* Registered automatically by this Strapi plugin; only add it yourself when building a schema outside
|
|
21
|
+
* Strapi (e.g. in tests).
|
|
22
|
+
*/
|
|
23
|
+
const nexusMiddlewarePlugin = () => (0, nexus_1.plugin)({
|
|
24
|
+
name: 'NexusMiddlewarePlugin',
|
|
25
|
+
description: 'The middleware plugin allows attaching middleware functions to individual fields.',
|
|
26
|
+
/**
|
|
27
|
+
* Defines the field definition types for the plugin.
|
|
28
|
+
*/
|
|
29
|
+
fieldDefTypes: fieldDefTypes,
|
|
30
|
+
/**
|
|
31
|
+
* Hook into field resolver creation to apply middleware.
|
|
32
|
+
*/
|
|
33
|
+
onCreateFieldResolver(config) {
|
|
34
|
+
const { fieldConfig } = config;
|
|
35
|
+
const { middlewares } = (0, resolve_field_1.resolveFieldPipeline)(config.parentTypeConfig.name, fieldConfig.name,
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
fieldConfig.extensions);
|
|
38
|
+
if (middlewares.length === 0) {
|
|
39
|
+
return undefined; // No middleware to apply
|
|
40
|
+
}
|
|
41
|
+
// Outermost first. Each middleware may call the next resolver with changed parent/args/context,
|
|
42
|
+
// which are passed on (same composition as `.test()`).
|
|
43
|
+
return (source, args, context, info, next) => {
|
|
44
|
+
const resolver = middlewares.reduceRight((inner, middleware) => middleware(inner), (s, a, c, i) => next(s, a, c, i));
|
|
45
|
+
return resolver(source, args, context, info);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
exports.nexusMiddlewarePlugin = nexusMiddlewarePlugin;
|
|
50
|
+
/**
|
|
51
|
+
* Example middleware that logs every resolve, including the result.
|
|
52
|
+
*
|
|
53
|
+
* Development only: it logs resolved values, which may contain personal data.
|
|
54
|
+
*/
|
|
55
|
+
const loggingMiddleware = (resolver) => {
|
|
56
|
+
return async (parent, args, context, info) => {
|
|
57
|
+
console.log(`Resolving field ${info.parentType.name}.${info.fieldName}`);
|
|
58
|
+
const result = await resolver(parent, args, context, info);
|
|
59
|
+
console.log(`Resolved field ${info.parentType.name}.${info.fieldName} with result:`, result);
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
exports.loggingMiddleware = loggingMiddleware;
|
package/dist/server/register.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const nexus_1 = require("nexus");
|
|
4
|
-
const utils_1 = require("./utils");
|
|
3
|
+
const nexus_1 = require("./nexus");
|
|
5
4
|
exports.default = async ({ strapi }) => {
|
|
6
5
|
const fileService = strapi
|
|
7
6
|
.plugin('burotwosix-graphql-plugin')
|
|
@@ -14,8 +13,8 @@ exports.default = async ({ strapi }) => {
|
|
|
14
13
|
];
|
|
15
14
|
graphQlExtensionService.use(() => ({
|
|
16
15
|
plugins: [
|
|
17
|
-
(0, nexus_1.
|
|
18
|
-
(0,
|
|
16
|
+
(0, nexus_1.nexusFieldAuthorizePlugin)(),
|
|
17
|
+
(0, nexus_1.nexusMiddlewarePlugin)()
|
|
19
18
|
]
|
|
20
19
|
}));
|
|
21
20
|
for (const extension of extensions) {
|
|
@@ -26,14 +25,14 @@ exports.default = async ({ strapi }) => {
|
|
|
26
25
|
typeDefs: (_b = (_a = extension === null || extension === void 0 ? void 0 : extension.typeDefs) === null || _a === void 0 ? void 0 : _a.call(extension)) !== null && _b !== void 0 ? _b : '',
|
|
27
26
|
types: [
|
|
28
27
|
...((_d = (_c = extension === null || extension === void 0 ? void 0 : extension.getTypes) === null || _c === void 0 ? void 0 : _c.call(extension)) !== null && _d !== void 0 ? _d : []),
|
|
29
|
-
...((_f = (_e = extension === null || extension === void 0 ? void 0 : extension.types) === null || _e === void 0 ? void 0 : _e.call(extension)) !== null && _f !== void 0 ? _f : [])
|
|
28
|
+
...((_f = (_e = extension === null || extension === void 0 ? void 0 : extension.types) === null || _e === void 0 ? void 0 : _e.call(extension)) !== null && _f !== void 0 ? _f : [])
|
|
30
29
|
],
|
|
31
30
|
plugins: (_h = (_g = extension === null || extension === void 0 ? void 0 : extension.plugins) === null || _g === void 0 ? void 0 : _g.call(extension)) !== null && _h !== void 0 ? _h : [],
|
|
32
31
|
resolvers: (_k = (_j = extension === null || extension === void 0 ? void 0 : extension.resolvers) === null || _j === void 0 ? void 0 : _j.call(extension)) !== null && _k !== void 0 ? _k : { Query: {}, Mutation: {} },
|
|
33
32
|
resolversConfig: {
|
|
34
33
|
...(_m = (_l = extension === null || extension === void 0 ? void 0 : extension.getResolverConfig) === null || _l === void 0 ? void 0 : _l.call(extension)) !== null && _m !== void 0 ? _m : {},
|
|
35
|
-
...(_p = (_o = extension === null || extension === void 0 ? void 0 : extension.resolversConfig) === null || _o === void 0 ? void 0 : _o.call(extension)) !== null && _p !== void 0 ? _p : {}
|
|
36
|
-
}
|
|
34
|
+
...(_p = (_o = extension === null || extension === void 0 ? void 0 : extension.resolversConfig) === null || _o === void 0 ? void 0 : _o.call(extension)) !== null && _p !== void 0 ? _p : {}
|
|
35
|
+
}
|
|
37
36
|
});
|
|
38
37
|
});
|
|
39
38
|
}
|
|
@@ -3,13 +3,14 @@ declare const _default: ({ strapi }: {
|
|
|
3
3
|
strapi: Strapi;
|
|
4
4
|
}) => {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Recursively searches for files in the graphql directory
|
|
8
|
-
* Returns an array of GraphQLExtension objects
|
|
6
|
+
* Load all extensions from `graphql` folders below `directoryPath` (recursively).
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Files are loaded in a stable order (sorted by name, depth-first), so the generated schema and the
|
|
9
|
+
* order of side effects such as `protectFields()` / `registerGlobalAuthorize()` calls are the same on
|
|
10
|
+
* every start.
|
|
11
|
+
*
|
|
12
|
+
* @param directoryPath - Usually `strapi.dirs.dist.src`.
|
|
12
13
|
*/
|
|
13
|
-
getAllFilesInGraphQlDirectory(directoryPath: string
|
|
14
|
+
getAllFilesInGraphQlDirectory(directoryPath: string): Promise<GraphQLExtension[]>;
|
|
14
15
|
};
|
|
15
16
|
export default _default;
|
|
@@ -25,44 +25,50 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
const fs_1 = require("fs");
|
|
27
27
|
const path = __importStar(require("path"));
|
|
28
|
+
const GRAPHQL_FOLDER = 'graphql';
|
|
29
|
+
const isInGraphQLFolder = (directoryPath) => directoryPath.endsWith(path.sep + GRAPHQL_FOLDER) || directoryPath.includes(path.sep + GRAPHQL_FOLDER + path.sep);
|
|
30
|
+
const isLoadableFile = (file) => file.endsWith('.js');
|
|
31
|
+
/**
|
|
32
|
+
* Turn a module's export into extensions. Builders (anything with `build()`) are built; arrays are flattened.
|
|
33
|
+
*/
|
|
34
|
+
const toExtensions = (graphqlModule) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const exported = (_a = graphqlModule === null || graphqlModule === void 0 ? void 0 : graphqlModule.default) !== null && _a !== void 0 ? _a : graphqlModule;
|
|
37
|
+
if (!exported)
|
|
38
|
+
return [];
|
|
39
|
+
const build = (item) => (item && typeof item.build === 'function' ? item.build() : item);
|
|
40
|
+
if (Array.isArray(exported)) {
|
|
41
|
+
return exported.map(build).filter(Boolean);
|
|
42
|
+
}
|
|
43
|
+
// Modules without a default export (e.g. one that only calls `protectFields`) yield an empty extension,
|
|
44
|
+
// which registers nothing.
|
|
45
|
+
return [build(exported)];
|
|
46
|
+
};
|
|
28
47
|
exports.default = ({ strapi }) => ({
|
|
29
48
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
49
|
+
* Load all extensions from `graphql` folders below `directoryPath` (recursively).
|
|
50
|
+
*
|
|
51
|
+
* Files are loaded in a stable order (sorted by name, depth-first), so the generated schema and the
|
|
52
|
+
* order of side effects such as `protectFields()` / `registerGlobalAuthorize()` calls are the same on
|
|
53
|
+
* every start.
|
|
33
54
|
*
|
|
34
|
-
* @param directoryPath
|
|
35
|
-
* @param collectedExtensions
|
|
55
|
+
* @param directoryPath - Usually `strapi.dirs.dist.src`.
|
|
36
56
|
*/
|
|
37
|
-
async getAllFilesInGraphQlDirectory(directoryPath
|
|
38
|
-
const fileEntries = await fs_1.promises.readdir(directoryPath);
|
|
39
|
-
|
|
40
|
-
const
|
|
57
|
+
async getAllFilesInGraphQlDirectory(directoryPath) {
|
|
58
|
+
const fileEntries = [...await fs_1.promises.readdir(directoryPath)].sort();
|
|
59
|
+
const collected = [];
|
|
60
|
+
for (const file of fileEntries) {
|
|
41
61
|
const filePath = path.join(directoryPath, file);
|
|
42
62
|
const fileStats = await fs_1.promises.stat(filePath);
|
|
43
63
|
if (fileStats.isDirectory()) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
const folderName = 'graphql';
|
|
48
|
-
if (!directoryPath.endsWith(path.sep + folderName) &&
|
|
49
|
-
!directoryPath.includes(path.sep + folderName + path.sep)) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const graphqlModule = require(filePath.replace('.js', ''));
|
|
53
|
-
if (!graphqlModule) {
|
|
54
|
-
return;
|
|
64
|
+
collected.push(...await this.getAllFilesInGraphQlDirectory(filePath));
|
|
65
|
+
continue;
|
|
55
66
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// If the extension is a builder (has a build function), call build() automatically
|
|
59
|
-
if (extension && typeof extension.build === 'function') {
|
|
60
|
-
extension = extension.build();
|
|
67
|
+
if (!isInGraphQLFolder(directoryPath) || !isLoadableFile(file)) {
|
|
68
|
+
continue;
|
|
61
69
|
}
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
await Promise.all(filePromises);
|
|
66
|
-
return collectedExtensions;
|
|
70
|
+
collected.push(...toExtensions(require(filePath)));
|
|
71
|
+
}
|
|
72
|
+
return collected;
|
|
67
73
|
}
|
|
68
74
|
});
|
|
@@ -2,7 +2,7 @@ declare const _default: {
|
|
|
2
2
|
fileService: ({ strapi }: {
|
|
3
3
|
strapi: Required<import("@strapi/types").Strapi>;
|
|
4
4
|
}) => {
|
|
5
|
-
getAllFilesInGraphQlDirectory(directoryPath: string
|
|
5
|
+
getAllFilesInGraphQlDirectory(directoryPath: string): Promise<import("..").GraphQLExtension[]>;
|
|
6
6
|
};
|
|
7
7
|
graphQlService: ({ strapi }: {
|
|
8
8
|
strapi: Required<import("@strapi/types").Strapi>;
|
package/dist/server/types.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ResolveContext } from './utils';
|
|
2
2
|
import type { GraphQLResolveInfo } from 'graphql';
|
|
3
3
|
export type Strapi = typeof strapi;
|
|
4
|
+
/**
|
|
5
|
+
* A GraphQL extension as registered with Strapi's GraphQL plugin. Created by
|
|
6
|
+
* {@link createGraphQLExtension} or by building a field builder.
|
|
7
|
+
*
|
|
8
|
+
* @see https://docs.strapi.io/dev-docs/plugins/graphql#customization
|
|
9
|
+
*/
|
|
4
10
|
export interface GraphQLExtension {
|
|
5
11
|
/**
|
|
6
12
|
* Called when the extension is initialized, use this to perform any initialization logic
|
|
@@ -16,7 +22,7 @@ export interface GraphQLExtension {
|
|
|
16
22
|
* Return array the types for the extension
|
|
17
23
|
* Use Nexus to define types
|
|
18
24
|
*
|
|
19
|
-
* @deprecated
|
|
25
|
+
* @deprecated Renamed to `types()`; same signature. Will be removed in the next major version.
|
|
20
26
|
*/
|
|
21
27
|
getTypes?: () => any[];
|
|
22
28
|
/**
|
|
@@ -43,7 +49,7 @@ export interface GraphQLExtension {
|
|
|
43
49
|
* This is used to configure middlewares and other resolver options
|
|
44
50
|
* @see https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#extending-the-schema
|
|
45
51
|
*
|
|
46
|
-
* @deprecated
|
|
52
|
+
* @deprecated Renamed to `resolversConfig()`; same signature. Will be removed in the next major version.
|
|
47
53
|
*/
|
|
48
54
|
getResolverConfig?: () => any;
|
|
49
55
|
/**
|
|
@@ -53,14 +59,20 @@ export interface GraphQLExtension {
|
|
|
53
59
|
*/
|
|
54
60
|
resolversConfig?: () => Record<string, ResolverConfigOption>;
|
|
55
61
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Strapi resolver configuration for a `Type.field` path.
|
|
64
|
+
*/
|
|
65
|
+
export type ResolverConfigOption<TParent = any, TArgs = any, TOutput = any> = {
|
|
66
|
+
/** `false` makes the field public; `{ scope }` requires these users-permissions actions */
|
|
67
|
+
auth?: boolean | {
|
|
68
|
+
scope: string[];
|
|
69
|
+
} | undefined;
|
|
70
|
+
policies?: (string | PolicyFunction<TParent, TArgs>)[];
|
|
71
|
+
middlewares?: MiddlewareFunction<TParent, TArgs, TOutput>[];
|
|
60
72
|
};
|
|
61
|
-
export type PolicyFunctionContext = {
|
|
62
|
-
parent:
|
|
63
|
-
args:
|
|
73
|
+
export type PolicyFunctionContext<TParent = any, TArgs = any> = {
|
|
74
|
+
parent: TParent;
|
|
75
|
+
args: TArgs;
|
|
64
76
|
context: ResolveContext;
|
|
65
77
|
info: GraphQLResolveInfo;
|
|
66
78
|
state: ResolveContext['state'];
|
|
@@ -69,7 +81,10 @@ export type PolicyFunctionContext = {
|
|
|
69
81
|
export type PolicyFunctionOptions = {
|
|
70
82
|
strapi: Strapi;
|
|
71
83
|
};
|
|
72
|
-
export type PolicyFunction = (context: PolicyFunctionContext, config: any, opts: PolicyFunctionOptions) => boolean | Promise<boolean>;
|
|
73
|
-
export type MiddlewareFunction = (next: MiddlewareNextFunction, parent:
|
|
74
|
-
export type MiddlewareNextFunction = (parent:
|
|
75
|
-
|
|
84
|
+
export type PolicyFunction<TParent = any, TArgs = any> = (context: PolicyFunctionContext<TParent, TArgs>, config: any, opts: PolicyFunctionOptions) => boolean | Promise<boolean>;
|
|
85
|
+
export type MiddlewareFunction<TParent = any, TArgs = any, TOutput = any> = (next: MiddlewareNextFunction<TParent, TArgs, TOutput>, parent: TParent, args: TArgs, context: ResolveContext, info: GraphQLResolveInfo) => TOutput | Promise<TOutput>;
|
|
86
|
+
export type MiddlewareNextFunction<TParent = any, TArgs = any, TOutput = any> = (parent: TParent, args: TArgs, context: ResolveContext, info: GraphQLResolveInfo) => Promise<TOutput>;
|
|
87
|
+
/**
|
|
88
|
+
* A GraphQL resolver with Strapi's resolver context.
|
|
89
|
+
*/
|
|
90
|
+
export type ResolverFunction<TParent = any, TArgs = any, TOutput = any> = (parent: TParent, args: TArgs, context: ResolveContext, info: GraphQLResolveInfo) => TOutput | Promise<TOutput>;
|
|
@@ -8,14 +8,18 @@ import { type ResolveArgs } from './types';
|
|
|
8
8
|
* Create a resolver for a collection of entities
|
|
9
9
|
* Returns a resolver that resolves the entities based on the content type UID
|
|
10
10
|
*
|
|
11
|
-
* @param contentType
|
|
11
|
+
* @param contentType - Content type UID of the entities.
|
|
12
|
+
* @returns A resolver taking `{ parent, args, context, info }` that applies Strapi's filters, sort and
|
|
13
|
+
* pagination and returns an entity response collection.
|
|
12
14
|
*/
|
|
13
15
|
export declare const createEntityCollectionResolver: <ContentType extends Object>(contentType: string) => (resolveArgs: ResolveArgs<ContentType>) => Promise<EntityResponseCollection<ContentType>>;
|
|
14
16
|
/**
|
|
15
17
|
* Create a resolver for a collection of entities related to a parent entity
|
|
16
18
|
* Returns a resolver that resolves the related entities based on the relation field name
|
|
17
19
|
*
|
|
18
|
-
* @param contentType
|
|
20
|
+
* @param contentType - Content type UID of the **parent** (owner of the relation).
|
|
21
|
+
* @deprecated Prefer {@link FieldExtensionBuilder.relation}, which also keeps the `find` permission scope
|
|
22
|
+
* and works on Strapi v5.
|
|
19
23
|
*/
|
|
20
24
|
export declare function createEntityRelationCollectionResolver<Parent extends object>(contentType: string): <K extends keyof Parent>(relationFieldName: K, resolveArgs: ResolveArgs<Parent>) => Promise<EntityResponseCollection<ExtractRelation<Parent, K>>>;
|
|
21
25
|
type EntityResponseCollection<T extends Object> = {
|
|
@@ -26,6 +30,13 @@ type EntityResponseCollection<T extends Object> = {
|
|
|
26
30
|
};
|
|
27
31
|
};
|
|
28
32
|
type ExtractRelation<Parent, K extends keyof Parent> = Parent[K] extends Array<infer U> ? U extends object ? U : never : Parent[K] extends object ? Parent[K] : never;
|
|
33
|
+
/**
|
|
34
|
+
* Wrap entities in Strapi's response collection (`{ nodes, info }`), the return shape of
|
|
35
|
+
* `XEntityResponseCollection` fields.
|
|
36
|
+
*
|
|
37
|
+
* @param args - The (transformed) field arguments, used for pagination info.
|
|
38
|
+
* @param resourceUID - Content type UID of the items.
|
|
39
|
+
*/
|
|
29
40
|
export declare function toEntityResponseCollection<T extends Object>(items: T[], { args, resourceUID }: {
|
|
30
41
|
args: Record<string, any>;
|
|
31
42
|
resourceUID: string;
|
|
@@ -10,7 +10,9 @@ exports.toEntityResponseCollection = exports.createEntityRelationCollectionResol
|
|
|
10
10
|
* Create a resolver for a collection of entities
|
|
11
11
|
* Returns a resolver that resolves the entities based on the content type UID
|
|
12
12
|
*
|
|
13
|
-
* @param contentType
|
|
13
|
+
* @param contentType - Content type UID of the entities.
|
|
14
|
+
* @returns A resolver taking `{ parent, args, context, info }` that applies Strapi's filters, sort and
|
|
15
|
+
* pagination and returns an entity response collection.
|
|
14
16
|
*/
|
|
15
17
|
const createEntityCollectionResolver = (contentType) => {
|
|
16
18
|
const { transformArgs } = strapi.plugin('graphql').service('builders').utils;
|
|
@@ -36,7 +38,9 @@ exports.createEntityCollectionResolver = createEntityCollectionResolver;
|
|
|
36
38
|
* Create a resolver for a collection of entities related to a parent entity
|
|
37
39
|
* Returns a resolver that resolves the related entities based on the relation field name
|
|
38
40
|
*
|
|
39
|
-
* @param contentType
|
|
41
|
+
* @param contentType - Content type UID of the **parent** (owner of the relation).
|
|
42
|
+
* @deprecated Prefer {@link FieldExtensionBuilder.relation}, which also keeps the `find` permission scope
|
|
43
|
+
* and works on Strapi v5.
|
|
40
44
|
*/
|
|
41
45
|
function createEntityRelationCollectionResolver(contentType) {
|
|
42
46
|
return async (relationFieldName, resolveArgs) => {
|
|
@@ -52,6 +56,13 @@ function createEntityRelationCollectionResolver(contentType) {
|
|
|
52
56
|
};
|
|
53
57
|
}
|
|
54
58
|
exports.createEntityRelationCollectionResolver = createEntityRelationCollectionResolver;
|
|
59
|
+
/**
|
|
60
|
+
* Wrap entities in Strapi's response collection (`{ nodes, info }`), the return shape of
|
|
61
|
+
* `XEntityResponseCollection` fields.
|
|
62
|
+
*
|
|
63
|
+
* @param args - The (transformed) field arguments, used for pagination info.
|
|
64
|
+
* @param resourceUID - Content type UID of the items.
|
|
65
|
+
*/
|
|
55
66
|
async function toEntityResponseCollection(items, { args, resourceUID }) {
|
|
56
67
|
const { toEntityResponseCollection } = strapi.plugin('graphql').service('format').returnTypes;
|
|
57
68
|
return toEntityResponseCollection(items, {
|