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.
Files changed (57) hide show
  1. package/README.md +458 -439
  2. package/dist/server/index.d.ts +14 -8
  3. package/dist/server/index.js +1 -0
  4. package/dist/server/nexus/index.d.ts +1 -0
  5. package/dist/server/nexus/index.js +17 -0
  6. package/dist/server/nexus/plugins/field-authorize.plugin.d.ts +36 -0
  7. package/dist/server/nexus/plugins/field-authorize.plugin.js +122 -0
  8. package/dist/server/nexus/plugins/index.d.ts +2 -0
  9. package/dist/server/nexus/plugins/index.js +18 -0
  10. package/dist/server/nexus/plugins/middleware.plugin.d.ts +25 -0
  11. package/dist/server/nexus/plugins/middleware.plugin.js +63 -0
  12. package/dist/server/register.js +6 -7
  13. package/dist/server/services/file-service.d.ts +7 -6
  14. package/dist/server/services/file-service.js +36 -30
  15. package/dist/server/services/index.d.ts +1 -1
  16. package/dist/server/types.d.ts +28 -13
  17. package/dist/server/utils/collection-type.d.ts +13 -2
  18. package/dist/server/utils/collection-type.js +13 -2
  19. package/dist/server/utils/field-builder/builder.d.ts +33 -0
  20. package/dist/server/utils/field-builder/builder.js +552 -0
  21. package/dist/server/utils/field-builder/helpers.d.ts +34 -0
  22. package/dist/server/utils/field-builder/helpers.js +78 -0
  23. package/dist/server/utils/field-builder/index.d.ts +3 -0
  24. package/dist/server/utils/field-builder/index.js +19 -0
  25. package/dist/server/utils/field-builder/types.d.ts +371 -0
  26. package/dist/server/utils/field-builder/types.js +2 -0
  27. package/dist/server/utils/graphql-extension.d.ts +29 -3
  28. package/dist/server/utils/graphql-extension.js +94 -26
  29. package/dist/server/utils/index.d.ts +3 -1
  30. package/dist/server/utils/index.js +12 -1
  31. package/dist/server/utils/loaders.d.ts +94 -0
  32. package/dist/server/utils/loaders.js +166 -0
  33. package/dist/server/utils/misc.d.ts +6 -0
  34. package/dist/server/utils/misc.js +6 -0
  35. package/dist/server/utils/override.d.ts +34 -1
  36. package/dist/server/utils/override.js +54 -26
  37. package/dist/server/utils/registry/field-protection.d.ts +43 -0
  38. package/dist/server/utils/registry/field-protection.js +65 -0
  39. package/dist/server/utils/registry/global-registry.d.ts +63 -0
  40. package/dist/server/utils/registry/global-registry.js +56 -0
  41. package/dist/server/utils/registry/index.d.ts +3 -0
  42. package/dist/server/utils/registry/index.js +19 -0
  43. package/dist/server/utils/registry/resolve-field.d.ts +1 -0
  44. package/dist/server/utils/registry/resolve-field.js +67 -0
  45. package/dist/server/utils/shadow-crud.d.ts +28 -1
  46. package/dist/server/utils/shadow-crud.js +32 -1
  47. package/dist/server/utils/single-type.d.ts +30 -1
  48. package/dist/server/utils/single-type.js +25 -0
  49. package/dist/server/utils/strapi-compat.d.ts +85 -0
  50. package/dist/server/utils/strapi-compat.js +194 -0
  51. package/dist/server/utils/types.d.ts +25 -1
  52. package/dist/tsconfig.server.tsbuildinfo +1 -1
  53. package/package.json +7 -4
  54. package/dist/server/utils/field-builder.d.ts +0 -31
  55. package/dist/server/utils/field-builder.js +0 -113
  56. package/dist/server/utils/middleware.d.ts +0 -17
  57. package/dist/server/utils/middleware.js +0 -77
@@ -1,5 +1,6 @@
1
1
  import type { GraphQLExtension } from './types';
2
- import type { Middleware, ResolveContext } from './utils';
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, collectedExtensions?: GraphQLExtension[]): Promise<GraphQLExtension[]>;
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
- * A function that determines if the current user is authorized to access this field.
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
  }
@@ -41,3 +41,4 @@ exports.default = {
41
41
  };
42
42
  __exportStar(require("./types"), exports);
43
43
  __exportStar(require("./utils"), exports);
44
+ __exportStar(require("./nexus"), exports);
@@ -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,2 @@
1
+ export * from './field-authorize.plugin';
2
+ export * from './middleware.plugin';
@@ -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;
@@ -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.fieldAuthorizePlugin)(),
18
- (0, utils_1.nexusMiddlewarePlugin)(),
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
- * Fetch all the files in the graphql directories within the given directory path
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
- * @param directoryPath
11
- * @param collectedExtensions
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, collectedExtensions?: GraphQLExtension[]): Promise<GraphQLExtension[]>;
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
- * Fetch all the files in the graphql directories within the given directory path
31
- * Recursively searches for files in the graphql directory
32
- * Returns an array of GraphQLExtension objects
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, collectedExtensions = []) {
38
- const fileEntries = await fs_1.promises.readdir(directoryPath);
39
- // Create an array of promises for all the files
40
- const filePromises = fileEntries.map(async (file) => {
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
- // Recursively get files from the directory
45
- return this.getAllFilesInGraphQlDirectory(filePath, collectedExtensions);
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
- // Support both ES module default export and CommonJS exports
57
- let extension = graphqlModule.default || graphqlModule;
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
- collectedExtensions.push(extension);
63
- });
64
- // Wait for all the promises to resolve
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, collectedExtensions?: import("..").GraphQLExtension[]): Promise<import("..").GraphQLExtension[]>;
5
+ getAllFilesInGraphQlDirectory(directoryPath: string): Promise<import("..").GraphQLExtension[]>;
6
6
  };
7
7
  graphQlService: ({ strapi }: {
8
8
  strapi: Required<import("@strapi/types").Strapi>;
@@ -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 Use types() instead
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 Use resolversConfig() instead
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
- export type ResolverConfigOption = {
57
- auth?: boolean | undefined;
58
- policies?: (string | PolicyFunction)[];
59
- middlewares?: MiddlewareFunction[];
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: any;
63
- args: any;
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: any, args: any, context: ResolveContext, info: GraphQLResolveInfo) => any | Promise<any>;
74
- export type MiddlewareNextFunction = (parent: any, args: any, context: ResolveContext, info: GraphQLResolveInfo) => Promise<any>;
75
- export type ResolverFunction = (parent: any, args: any, context: ResolveContext, info: GraphQLResolveInfo) => any | Promise<any>;
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 The content type UID of the entities
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 The content type UID of the related entities
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 The content type UID of the entities
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 The content type UID of the related entities
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, {