buro26-strapi-graphql 0.0.26 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +458 -439
- package/dist/server/index.d.ts +14 -8
- package/dist/server/index.js +1 -0
- package/dist/server/nexus/index.d.ts +1 -0
- package/dist/server/nexus/index.js +17 -0
- package/dist/server/nexus/plugins/field-authorize.plugin.d.ts +36 -0
- package/dist/server/nexus/plugins/field-authorize.plugin.js +122 -0
- package/dist/server/nexus/plugins/index.d.ts +2 -0
- package/dist/server/nexus/plugins/index.js +18 -0
- package/dist/server/nexus/plugins/middleware.plugin.d.ts +25 -0
- package/dist/server/nexus/plugins/middleware.plugin.js +63 -0
- package/dist/server/register.js +6 -7
- package/dist/server/services/file-service.d.ts +7 -6
- package/dist/server/services/file-service.js +36 -30
- package/dist/server/services/index.d.ts +1 -1
- package/dist/server/types.d.ts +28 -13
- package/dist/server/utils/collection-type.d.ts +13 -2
- package/dist/server/utils/collection-type.js +13 -2
- package/dist/server/utils/field-builder/builder.d.ts +33 -0
- package/dist/server/utils/field-builder/builder.js +552 -0
- package/dist/server/utils/field-builder/helpers.d.ts +34 -0
- package/dist/server/utils/field-builder/helpers.js +78 -0
- package/dist/server/utils/field-builder/index.d.ts +3 -0
- package/dist/server/utils/field-builder/index.js +19 -0
- package/dist/server/utils/field-builder/types.d.ts +371 -0
- package/dist/server/utils/field-builder/types.js +2 -0
- package/dist/server/utils/graphql-extension.d.ts +29 -3
- package/dist/server/utils/graphql-extension.js +94 -26
- package/dist/server/utils/index.d.ts +3 -1
- package/dist/server/utils/index.js +12 -1
- package/dist/server/utils/loaders.d.ts +94 -0
- package/dist/server/utils/loaders.js +166 -0
- package/dist/server/utils/misc.d.ts +6 -0
- package/dist/server/utils/misc.js +6 -0
- package/dist/server/utils/override.d.ts +34 -1
- package/dist/server/utils/override.js +54 -26
- package/dist/server/utils/registry/field-protection.d.ts +43 -0
- package/dist/server/utils/registry/field-protection.js +65 -0
- package/dist/server/utils/registry/global-registry.d.ts +63 -0
- package/dist/server/utils/registry/global-registry.js +56 -0
- package/dist/server/utils/registry/index.d.ts +3 -0
- package/dist/server/utils/registry/index.js +19 -0
- package/dist/server/utils/registry/resolve-field.d.ts +1 -0
- package/dist/server/utils/registry/resolve-field.js +67 -0
- package/dist/server/utils/shadow-crud.d.ts +28 -1
- package/dist/server/utils/shadow-crud.js +32 -1
- package/dist/server/utils/single-type.d.ts +30 -1
- package/dist/server/utils/single-type.js +25 -0
- package/dist/server/utils/strapi-compat.d.ts +85 -0
- package/dist/server/utils/strapi-compat.js +194 -0
- package/dist/server/utils/types.d.ts +25 -1
- package/dist/tsconfig.server.tsbuildinfo +1 -1
- package/package.json +7 -4
- package/dist/server/utils/field-builder.d.ts +0 -31
- package/dist/server/utils/field-builder.js +0 -113
- package/dist/server/utils/middleware.d.ts +0 -17
- package/dist/server/utils/middleware.js +0 -77
|
@@ -0,0 +1,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,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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {};
|