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
@@ -0,0 +1,63 @@
1
+ import type { ResolverFunction } from '../../types';
2
+ import type { Middleware } from '../../nexus';
3
+ /**
4
+ * Information about a field, passed to matchers of global middleware/authorization.
5
+ *
6
+ * Matchers are evaluated when the GraphQL schema is built (not when a field builder is built), so
7
+ * registration order no longer matters.
8
+ */
9
+ export type FieldMatcherInfo = {
10
+ /** GraphQL parent type name, e.g. `Query` or `Customer` */
11
+ type: string;
12
+ fieldName: string;
13
+ /** Tags set via the field builder (`.tag()`); empty for fields not created by the builder */
14
+ tags: string[];
15
+ /** Content type UID when the parent type is a Strapi content type, e.g. `api::customer.customer` */
16
+ contentType?: string;
17
+ /** True when the field was created with `createGraphQLFieldBuilder()` */
18
+ isBuilderField: boolean;
19
+ };
20
+ /**
21
+ * Decides whether a global middleware/authorization applies to a field. Called once per field when the
22
+ * schema is built, not per request.
23
+ */
24
+ export type FieldMatcher = (info: FieldMatcherInfo) => boolean;
25
+ /**
26
+ * Options for {@link registerGlobalAuthorize} and {@link registerGlobalMiddleware}.
27
+ */
28
+ export type GlobalRegistrationOptions = {
29
+ /**
30
+ * Also apply to fields not created by the field builder: Strapi's generated fields, `overrideField`
31
+ * and plain Nexus fields. Matchers then run for **every** field in the schema, so keep them cheap and
32
+ * specific (use `contentType`, `type` or `fieldName`).
33
+ *
34
+ * @default false
35
+ */
36
+ includeNonBuilderFields?: boolean;
37
+ };
38
+ /**
39
+ * Register a middleware for all fields that match. Global middlewares wrap field middlewares (they run
40
+ * first). Matched when the schema is built, so registration order relative to fields does not matter;
41
+ * register before Strapi's bootstrap (i.e. at module level in a `graphql` folder).
42
+ *
43
+ * @param matcher - Decides per field; see {@link FieldMatcherInfo}.
44
+ * @param middleware - `(next) => resolver`, as in `.middleware()`.
45
+ * @param options - See {@link GlobalRegistrationOptions}.
46
+ *
47
+ * @example
48
+ * registerGlobalMiddleware(({ tags }) => tags.includes('audit'), auditLog)
49
+ */
50
+ export declare function registerGlobalMiddleware(matcher: FieldMatcher, middleware: Middleware, options?: GlobalRegistrationOptions): void;
51
+ /**
52
+ * Register an authorization check for all fields that match. Global checks run before the field's own
53
+ * checks; all must return `true`. Matched when the schema is built, so registration order does not matter.
54
+ *
55
+ * @param matcher - Decides per field; see {@link FieldMatcherInfo}.
56
+ * @param authorize - Return `true` to allow, `false` or an `Error` to deny.
57
+ * @param options - See {@link GlobalRegistrationOptions}.
58
+ *
59
+ * @example
60
+ * // Rate limit every field, including Strapi's generated ones
61
+ * registerGlobalAuthorize(() => true, rateLimit, { includeNonBuilderFields: true })
62
+ */
63
+ export declare function registerGlobalAuthorize(matcher: FieldMatcher, authorize: ResolverFunction, options?: GlobalRegistrationOptions): void;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resetGlobalRegistry = exports.getGlobalAuthorizes = exports.getGlobalMiddlewares = exports.registerGlobalAuthorize = exports.registerGlobalMiddleware = void 0;
4
+ const globalMiddlewares = [];
5
+ const globalAuthorizes = [];
6
+ /**
7
+ * Register a middleware for all fields that match. Global middlewares wrap field middlewares (they run
8
+ * first). Matched when the schema is built, so registration order relative to fields does not matter;
9
+ * register before Strapi's bootstrap (i.e. at module level in a `graphql` folder).
10
+ *
11
+ * @param matcher - Decides per field; see {@link FieldMatcherInfo}.
12
+ * @param middleware - `(next) => resolver`, as in `.middleware()`.
13
+ * @param options - See {@link GlobalRegistrationOptions}.
14
+ *
15
+ * @example
16
+ * registerGlobalMiddleware(({ tags }) => tags.includes('audit'), auditLog)
17
+ */
18
+ function registerGlobalMiddleware(matcher, middleware, options = {}) {
19
+ globalMiddlewares.push({ matcher, value: middleware, options });
20
+ }
21
+ exports.registerGlobalMiddleware = registerGlobalMiddleware;
22
+ /**
23
+ * Register an authorization check for all fields that match. Global checks run before the field's own
24
+ * checks; all must return `true`. Matched when the schema is built, so registration order does not matter.
25
+ *
26
+ * @param matcher - Decides per field; see {@link FieldMatcherInfo}.
27
+ * @param authorize - Return `true` to allow, `false` or an `Error` to deny.
28
+ * @param options - See {@link GlobalRegistrationOptions}.
29
+ *
30
+ * @example
31
+ * // Rate limit every field, including Strapi's generated ones
32
+ * registerGlobalAuthorize(() => true, rateLimit, { includeNonBuilderFields: true })
33
+ */
34
+ function registerGlobalAuthorize(matcher, authorize, options = {}) {
35
+ globalAuthorizes.push({ matcher, value: authorize, options });
36
+ }
37
+ exports.registerGlobalAuthorize = registerGlobalAuthorize;
38
+ const matching = (entries, info) => entries
39
+ .filter(({ matcher, options }) => (info.isBuilderField || options.includeNonBuilderFields) && matcher(info))
40
+ .map(({ value }) => value);
41
+ /** @internal Used by the Nexus plugins. */
42
+ function getGlobalMiddlewares(info) {
43
+ return matching(globalMiddlewares, info);
44
+ }
45
+ exports.getGlobalMiddlewares = getGlobalMiddlewares;
46
+ /** @internal Used by the Nexus plugins. */
47
+ function getGlobalAuthorizes(info) {
48
+ return matching(globalAuthorizes, info);
49
+ }
50
+ exports.getGlobalAuthorizes = getGlobalAuthorizes;
51
+ /** @internal Only for tests. */
52
+ function resetGlobalRegistry() {
53
+ globalMiddlewares.length = 0;
54
+ globalAuthorizes.length = 0;
55
+ }
56
+ exports.resetGlobalRegistry = resetGlobalRegistry;
@@ -0,0 +1,3 @@
1
+ export * from './global-registry';
2
+ export * from './field-protection';
3
+ export * from './resolve-field';
@@ -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("./global-registry"), exports);
18
+ __exportStar(require("./field-protection"), exports);
19
+ __exportStar(require("./resolve-field"), exports);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveFieldPipeline = void 0;
4
+ const strapi_compat_1 = require("../strapi-compat");
5
+ const global_registry_1 = require("./global-registry");
6
+ const field_protection_1 = require("./field-protection");
7
+ /**
8
+ * `authorize`/`middlewares` can be set in `extensions` or as top-level Nexus field options
9
+ * (`t.field('x', { authorize })`, as declared in the plugins' typegen). Both are honoured.
10
+ */
11
+ const ownAuthorize = (extensions) => {
12
+ var _a, _b, _c;
13
+ const candidate = (_a = extensions === null || extensions === void 0 ? void 0 : extensions.authorize) !== null && _a !== void 0 ? _a : (_c = (_b = extensions === null || extensions === void 0 ? void 0 : extensions.nexus) === null || _b === void 0 ? void 0 : _b.config) === null || _c === void 0 ? void 0 : _c.authorize;
14
+ return typeof candidate === 'function' ? candidate : undefined;
15
+ };
16
+ const ownMiddlewares = (extensions) => {
17
+ var _a, _b, _c;
18
+ const candidate = (_a = extensions === null || extensions === void 0 ? void 0 : extensions.middlewares) !== null && _a !== void 0 ? _a : (_c = (_b = extensions === null || extensions === void 0 ? void 0 : extensions.nexus) === null || _b === void 0 ? void 0 : _b.config) === null || _c === void 0 ? void 0 : _c.middlewares;
19
+ return Array.isArray(candidate) ? candidate : [];
20
+ };
21
+ const contentTypeOf = (typeName) => {
22
+ try {
23
+ return (0, strapi_compat_1.uidOfTypeName)(typeName);
24
+ }
25
+ catch {
26
+ // Strapi/graphql plugin not available (e.g. in unit tests)
27
+ return undefined;
28
+ }
29
+ };
30
+ /**
31
+ * Everything that must run around a field resolver, in order:
32
+ *
33
+ * 1. authentication (`withAuth(true)` on builder fields)
34
+ * 2. authorization: global authorizes → the field's own `authorize` → `protectFields` authorizes
35
+ * 3. middlewares: global middlewares (outermost) → the field's own → `protectFields` middlewares
36
+ *
37
+ * Evaluated once per field when the schema is built.
38
+ *
39
+ * @internal Used by the Nexus plugins and `.test()`.
40
+ */
41
+ function resolveFieldPipeline(typeName, fieldName, extensions) {
42
+ var _a, _b, _c;
43
+ const contentType = contentTypeOf(typeName);
44
+ const info = {
45
+ type: typeName,
46
+ fieldName,
47
+ tags: (_b = (_a = extensions === null || extensions === void 0 ? void 0 : extensions.builder) === null || _a === void 0 ? void 0 : _a.tags) !== null && _b !== void 0 ? _b : [],
48
+ contentType,
49
+ isBuilderField: Boolean(extensions === null || extensions === void 0 ? void 0 : extensions.builder),
50
+ };
51
+ const protections = (0, field_protection_1.getFieldProtections)(contentType, fieldName);
52
+ return {
53
+ requireAuth: Boolean((_c = extensions === null || extensions === void 0 ? void 0 : extensions.builder) === null || _c === void 0 ? void 0 : _c.requireAuth),
54
+ isProtected: protections.length > 0,
55
+ authorizes: [
56
+ ...(0, global_registry_1.getGlobalAuthorizes)(info),
57
+ ...(ownAuthorize(extensions) ? [ownAuthorize(extensions)] : []),
58
+ ...protections.flatMap((p) => p.authorizes),
59
+ ],
60
+ middlewares: [
61
+ ...(0, global_registry_1.getGlobalMiddlewares)(info),
62
+ ...ownMiddlewares(extensions),
63
+ ...protections.flatMap((p) => p.middlewares),
64
+ ],
65
+ };
66
+ }
67
+ exports.resolveFieldPipeline = resolveFieldPipeline;
@@ -1,3 +1,5 @@
1
+ import type { ResolverFunction } from '../types';
2
+ import type { Middleware } from '../nexus';
1
3
  type Action = 'create' | 'update' | 'delete' | 'findOne' | 'find';
2
4
  interface FieldOperations<T> {
3
5
  disable(): this;
@@ -9,6 +11,16 @@ interface FieldOperations<T> {
9
11
  hasInputEnabled(): boolean;
10
12
  hasOutputEnabled(): boolean;
11
13
  hasFiltersEnabled(): boolean;
14
+ /**
15
+ * Add an authorization check to the generated output field. Same as
16
+ * `protectFields(uid, [field]).authorize(fn)`; see {@link protectFields}.
17
+ */
18
+ authorize(fn: ResolverFunction<T>): this;
19
+ /**
20
+ * Add a middleware to the generated output field. Same as
21
+ * `protectFields(uid, [field]).middleware(fn)`; see {@link protectFields}.
22
+ */
23
+ middleware(fn: Middleware<T>): this;
12
24
  }
13
25
  interface ShadowCRUD<T> {
14
26
  disable(): this;
@@ -16,7 +28,8 @@ interface ShadowCRUD<T> {
16
28
  disableAction(action: Action): this;
17
29
  disableQueries(): this;
18
30
  disableMutations(): this;
19
- field<K extends keyof T>(name: K): FieldOperations<T[K]>;
31
+ /** Configure one attribute; the returned object also supports `.authorize()` and `.middleware()`. */
32
+ field<K extends keyof T & string>(name: K): FieldOperations<T>;
20
33
  isEnabled(): boolean;
21
34
  isDisabled(): boolean;
22
35
  areQueriesEnabled(): boolean;
@@ -26,5 +39,19 @@ interface ShadowCRUD<T> {
26
39
  isActionEnabled(action: Action): boolean;
27
40
  isActionDisabled(action: Action): boolean;
28
41
  }
42
+ /**
43
+ * Typed access to Strapi's shadow CRUD configuration: disable generated queries, mutations or fields.
44
+ * Call it in an extension's `init()` or a builder's `.init()`.
45
+ *
46
+ * @typeParam Type - Entity type, so `.field()` only accepts its keys.
47
+ * @param contentTypeUID - Content type UID.
48
+ *
49
+ * @example
50
+ * shadowCRUD<Customer>('api::customer.customer').disableActions(['create', 'update', 'delete'])
51
+ * shadowCRUD<Customer>('api::customer.customer').field('internalNotes').disableFilters()
52
+ * shadowCRUD<Customer>('api::customer.customer').field('iban').authorize(isSelf)
53
+ *
54
+ * @see https://docs.strapi.io/dev-docs/plugins/graphql#shadow-crud
55
+ */
29
56
  export declare const shadowCRUD: <Type extends unknown = null>(contentTypeUID: string) => ShadowCRUD<Type>;
30
57
  export {};
@@ -1,8 +1,39 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.shadowCRUD = void 0;
4
+ const field_protection_1 = require("./registry/field-protection");
5
+ /**
6
+ * Typed access to Strapi's shadow CRUD configuration: disable generated queries, mutations or fields.
7
+ * Call it in an extension's `init()` or a builder's `.init()`.
8
+ *
9
+ * @typeParam Type - Entity type, so `.field()` only accepts its keys.
10
+ * @param contentTypeUID - Content type UID.
11
+ *
12
+ * @example
13
+ * shadowCRUD<Customer>('api::customer.customer').disableActions(['create', 'update', 'delete'])
14
+ * shadowCRUD<Customer>('api::customer.customer').field('internalNotes').disableFilters()
15
+ * shadowCRUD<Customer>('api::customer.customer').field('iban').authorize(isSelf)
16
+ *
17
+ * @see https://docs.strapi.io/dev-docs/plugins/graphql#shadow-crud
18
+ */
4
19
  const shadowCRUD = (contentTypeUID) => {
5
20
  const extensionService = strapi.plugin('graphql').service('extension');
6
- return extensionService.shadowCRUD(contentTypeUID);
21
+ const crud = extensionService.shadowCRUD(contentTypeUID);
22
+ return Object.assign(Object.create(crud), {
23
+ field(name) {
24
+ const fieldOperations = crud.field(name);
25
+ const extended = Object.assign(Object.create(fieldOperations), {
26
+ authorize(fn) {
27
+ (0, field_protection_1.protectFields)(contentTypeUID, [name]).authorize(fn);
28
+ return extended;
29
+ },
30
+ middleware(fn) {
31
+ (0, field_protection_1.protectFields)(contentTypeUID, [name]).middleware(fn);
32
+ return extended;
33
+ },
34
+ });
35
+ return extended;
36
+ },
37
+ });
7
38
  };
8
39
  exports.shadowCRUD = shadowCRUD;
@@ -7,10 +7,29 @@ import { Entity, ResolveArgs } from './types';
7
7
  type ResolveEntityArgs = Omit<ResolveArgs, 'args'> & {
8
8
  args: {
9
9
  id: string;
10
+ } | {
11
+ documentId: string;
10
12
  };
11
13
  };
14
+ /**
15
+ * Resolve a single entity with Strapi's own `findOne` query resolver.
16
+ * On Strapi v5 `args` must contain `documentId` instead of `id`.
17
+ *
18
+ * @throws Error on Strapi v5 when `args.documentId` is missing.
19
+ */
12
20
  export declare function resolveEntity<T extends Object>(contentType: string, resolveArgs: ResolveEntityArgs): Promise<Entity<T> | null>;
13
- export declare const resolveEntityRelation: <T extends Object>(contentType: string, relationFieldName: keyof T, resolveArgs: ResolveArgs) => Promise<Entity<T>[]>;
21
+ /**
22
+ * Resolve a relation with Strapi's own association resolver (validation, sanitizing and draft/publish
23
+ * handling included). Returns Strapi's response wrapper: `{ value, info }` for to-one relations and
24
+ * `{ nodes, info }` for to-many relations. Prefer `.relation()` on the field builder for relation fields.
25
+ */
26
+ export declare const resolveEntityRelation: <T extends Object, R extends Object = any>(contentType: string, relationFieldName: keyof T, resolveArgs: ResolveArgs) => Promise<EntityResponse<R> | {
27
+ nodes: R[];
28
+ info: {
29
+ args: Record<string, any>;
30
+ resourceUID: string;
31
+ };
32
+ }>;
14
33
  type EntityResponse<T extends Object> = {
15
34
  value: Entity<T> | null;
16
35
  info: {
@@ -18,6 +37,16 @@ type EntityResponse<T extends Object> = {
18
37
  resourceUID: string;
19
38
  };
20
39
  };
40
+ /**
41
+ * Wrap an entity in Strapi's entity response (`{ value, info }`), the return shape of `XEntityResponse`
42
+ * fields such as single-entity queries.
43
+ *
44
+ * @param args - The field arguments, used by Strapi when resolving nested fields.
45
+ * @param resourceUID - Content type UID of `item`.
46
+ *
47
+ * @remarks Strapi v5 to-one relation fields expect the bare entity; use {@link toRelationValue} or
48
+ * {@link FieldExtensionBuilder.relation} for relations.
49
+ */
21
50
  export declare function toEntityResponse<T extends Object>(item: T | null, { args, resourceUID }: {
22
51
  args: Record<string, any>;
23
52
  resourceUID: string;
@@ -6,10 +6,20 @@
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.toEntityResponse = exports.resolveEntityRelation = exports.resolveEntity = void 0;
9
+ const strapi_compat_1 = require("./strapi-compat");
10
+ /**
11
+ * Resolve a single entity with Strapi's own `findOne` query resolver.
12
+ * On Strapi v5 `args` must contain `documentId` instead of `id`.
13
+ *
14
+ * @throws Error on Strapi v5 when `args.documentId` is missing.
15
+ */
9
16
  async function resolveEntity(contentType, resolveArgs) {
10
17
  const { transformArgs } = strapi.plugin('graphql').service('builders').utils;
11
18
  const { toEntityResponse } = strapi.plugin('graphql').service('format').returnTypes;
12
19
  const { buildQueriesResolvers } = strapi.plugin('graphql').service('builders').get('content-api');
20
+ if ((0, strapi_compat_1.isStrapiV5)() && !('documentId' in resolveArgs.args)) {
21
+ throw new Error(`[buro26-strapi-graphql] resolveEntity("${contentType}") needs args.documentId on Strapi v5 (got args.id)`);
22
+ }
13
23
  const transformedArgs = transformArgs(resolveArgs.args, { contentType });
14
24
  const { findOne } = buildQueriesResolvers({ contentType });
15
25
  const item = await findOne(resolveArgs.parent, transformedArgs, resolveArgs.context);
@@ -19,6 +29,11 @@ async function resolveEntity(contentType, resolveArgs) {
19
29
  });
20
30
  }
21
31
  exports.resolveEntity = resolveEntity;
32
+ /**
33
+ * Resolve a relation with Strapi's own association resolver (validation, sanitizing and draft/publish
34
+ * handling included). Returns Strapi's response wrapper: `{ value, info }` for to-one relations and
35
+ * `{ nodes, info }` for to-many relations. Prefer `.relation()` on the field builder for relation fields.
36
+ */
22
37
  const resolveEntityRelation = async (contentType, relationFieldName, resolveArgs) => {
23
38
  const { buildAssociationResolver } = strapi.plugin('graphql').service('builders').get('content-api');
24
39
  const find = buildAssociationResolver({
@@ -28,6 +43,16 @@ const resolveEntityRelation = async (contentType, relationFieldName, resolveArgs
28
43
  return find(resolveArgs.parent, resolveArgs.args, resolveArgs.context);
29
44
  };
30
45
  exports.resolveEntityRelation = resolveEntityRelation;
46
+ /**
47
+ * Wrap an entity in Strapi's entity response (`{ value, info }`), the return shape of `XEntityResponse`
48
+ * fields such as single-entity queries.
49
+ *
50
+ * @param args - The field arguments, used by Strapi when resolving nested fields.
51
+ * @param resourceUID - Content type UID of `item`.
52
+ *
53
+ * @remarks Strapi v5 to-one relation fields expect the bare entity; use {@link toRelationValue} or
54
+ * {@link FieldExtensionBuilder.relation} for relations.
55
+ */
31
56
  async function toEntityResponse(item, { args, resourceUID }) {
32
57
  const { toEntityResponse } = strapi.plugin('graphql').service('format').returnTypes;
33
58
  return toEntityResponse(item, {
@@ -0,0 +1,85 @@
1
+ import type { GraphQLResolveInfo } from 'graphql';
2
+ import type { ResolveContext } from './types';
3
+ type AnyRecord = Record<string, any>;
4
+ /**
5
+ * True when running on Strapi v5. The document service (`strapi.documents`) only exists in v5.
6
+ */
7
+ export declare const isStrapiV5: () => boolean;
8
+ /**
9
+ * Whether the GraphQL plugin runs in v4 compatibility mode (only relevant on v5).
10
+ */
11
+ export declare const isV4CompatibilityMode: () => boolean;
12
+ /**
13
+ * GraphQL object type name Strapi generates for a content type, e.g. `api::customer.customer` → `Customer`.
14
+ */
15
+ export declare const typeNameOf: (uid: string) => string;
16
+ /**
17
+ * Reverse lookup of {@link typeNameOf}: the content type UID behind a generated GraphQL type name.
18
+ * Built lazily (content types do not change at runtime).
19
+ */
20
+ export declare const uidOfTypeName: (typeName: string) => string | undefined;
21
+ /**
22
+ * One GraphQL field Strapi generates for a relation attribute.
23
+ *
24
+ * @see {@link relationFieldSpecs}
25
+ */
26
+ export type RelationFieldSpec = {
27
+ /** Field name on the parent type */
28
+ name: string;
29
+ /** Nexus output type */
30
+ type: unknown;
31
+ args?: AnyRecord;
32
+ resolve: (parent: unknown, args: AnyRecord, context: ResolveContext, info: GraphQLResolveInfo) => unknown;
33
+ /** `Type.field` paths Strapi protects with the `<target>.find` permission scope */
34
+ resolverConfigPaths: string[];
35
+ };
36
+ /**
37
+ * Describes the GraphQL field(s) Strapi itself generates for a regular (non-morph) relation, so we can
38
+ * (re)define them with exactly the same type, arguments, resolver and permission scope.
39
+ *
40
+ * - v4: to-one → `XEntityResponse`, to-many → `XRelationResponseCollection` (with filters/pagination args).
41
+ * - v5: to-one → `X` (unwrapped), to-many → `[X]!` (unwrapped `nodes`) **and** `<field>_connection`
42
+ * returning `XRelationResponseCollection`.
43
+ *
44
+ * All variants use Strapi's own `buildAssociationResolver`, so query validation, output sanitizing and
45
+ * draft/publish handling stay identical to the generated field.
46
+ *
47
+ * Low-level building block for {@link FieldExtensionBuilder.relation} and `overrideField`; prefer those.
48
+ *
49
+ * @param resolveOverride - Replaces Strapi's association resolver. It must return Strapi's response
50
+ * wrapper (`{ value }` / `{ nodes }`); v5 unwrapping is applied on top.
51
+ * @throws Error when the content type is unknown or the attribute is not a regular relation.
52
+ */
53
+ export declare const relationFieldSpecs: (contentTypeUID: string, attributeName: string, resolveOverride?: RelationFieldSpec['resolve']) => RelationFieldSpec[];
54
+ /**
55
+ * Resolver config that applies Strapi's own permission scope (`<target>.find`) to relation fields,
56
+ * exactly like the generated relation fields do. Pass it to an extension's `resolversConfig` when you
57
+ * define relation fields yourself.
58
+ *
59
+ * @returns A map of `Type.field` paths to `{ auth: { scope } }`.
60
+ */
61
+ export declare const relationResolversConfig: (contentTypeUID: string, attributeName: string) => {
62
+ [k: string]: {
63
+ auth: {
64
+ scope: string[];
65
+ };
66
+ };
67
+ };
68
+ /**
69
+ * Sanitize raw database output for the content API (removes private/password fields and fields the
70
+ * current auth may not read). v4: `@strapi/utils` sanitize, v5: `strapi.contentAPI.sanitize`.
71
+ *
72
+ * Use it on data from {@link loaders} or `strapi.db.query()` before returning it from a resolver.
73
+ *
74
+ * @param uid - Content type of `data`.
75
+ * @param context - The resolver context; its `state.auth` decides what may be read.
76
+ */
77
+ export declare const sanitizeOutput: <T>(data: T, uid: string, context: ResolveContext) => Promise<T>;
78
+ /**
79
+ * Wrap a single entity the way the given relation field expects: an entity response in v4, the bare
80
+ * entity in v5.
81
+ *
82
+ * @param targetUID - Content type of `entity`.
83
+ */
84
+ export declare const toRelationValue: (entity: unknown, targetUID: string) => unknown;
85
+ export {};