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,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toRelationValue = exports.sanitizeOutput = exports.relationResolversConfig = exports.relationFieldSpecs = exports.resetTypeNameCache = exports.uidOfTypeName = exports.typeNameOf = exports.isV4CompatibilityMode = exports.isStrapiV5 = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Small compatibility layer over the parts of `@strapi/plugin-graphql` that differ between Strapi v4 and v5.
|
|
6
|
+
*
|
|
7
|
+
* Everything that needs to know "which Strapi is this?" should go through here, so the rest of the
|
|
8
|
+
* package can stay version-agnostic.
|
|
9
|
+
*/
|
|
10
|
+
const nexus_1 = require("nexus");
|
|
11
|
+
const getGraphQLService = (name) => strapi.plugin('graphql').service(name);
|
|
12
|
+
/**
|
|
13
|
+
* True when running on Strapi v5. The document service (`strapi.documents`) only exists in v5.
|
|
14
|
+
*/
|
|
15
|
+
const isStrapiV5 = () => typeof strapi.documents === 'function';
|
|
16
|
+
exports.isStrapiV5 = isStrapiV5;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the GraphQL plugin runs in v4 compatibility mode (only relevant on v5).
|
|
19
|
+
*/
|
|
20
|
+
const isV4CompatibilityMode = () => (0, exports.isStrapiV5)() && Boolean(strapi.plugin('graphql').config('v4CompatibilityMode', false));
|
|
21
|
+
exports.isV4CompatibilityMode = isV4CompatibilityMode;
|
|
22
|
+
const getModel = (uid) => {
|
|
23
|
+
const model = strapi.getModel(uid);
|
|
24
|
+
if (!model) {
|
|
25
|
+
throw new Error(`[buro26-strapi-graphql] Unknown content type "${uid}"`);
|
|
26
|
+
}
|
|
27
|
+
return model;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* GraphQL object type name Strapi generates for a content type, e.g. `api::customer.customer` → `Customer`.
|
|
31
|
+
*/
|
|
32
|
+
const typeNameOf = (uid) => getGraphQLService('utils').naming.getTypeName(getModel(uid));
|
|
33
|
+
exports.typeNameOf = typeNameOf;
|
|
34
|
+
let typeNameToUidCache = null;
|
|
35
|
+
/**
|
|
36
|
+
* Reverse lookup of {@link typeNameOf}: the content type UID behind a generated GraphQL type name.
|
|
37
|
+
* Built lazily (content types do not change at runtime).
|
|
38
|
+
*/
|
|
39
|
+
const uidOfTypeName = (typeName) => {
|
|
40
|
+
var _a;
|
|
41
|
+
if (!typeNameToUidCache) {
|
|
42
|
+
typeNameToUidCache = new Map();
|
|
43
|
+
const { naming } = getGraphQLService('utils');
|
|
44
|
+
for (const contentType of Object.values((_a = strapi.contentTypes) !== null && _a !== void 0 ? _a : {})) {
|
|
45
|
+
try {
|
|
46
|
+
typeNameToUidCache.set(naming.getTypeName(contentType), contentType.uid);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Content types that are not exposed in GraphQL can be ignored
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return typeNameToUidCache.get(typeName);
|
|
54
|
+
};
|
|
55
|
+
exports.uidOfTypeName = uidOfTypeName;
|
|
56
|
+
/** @internal Only for tests. */
|
|
57
|
+
const resetTypeNameCache = () => {
|
|
58
|
+
typeNameToUidCache = null;
|
|
59
|
+
};
|
|
60
|
+
exports.resetTypeNameCache = resetTypeNameCache;
|
|
61
|
+
/**
|
|
62
|
+
* Describes the GraphQL field(s) Strapi itself generates for a regular (non-morph) relation, so we can
|
|
63
|
+
* (re)define them with exactly the same type, arguments, resolver and permission scope.
|
|
64
|
+
*
|
|
65
|
+
* - v4: to-one → `XEntityResponse`, to-many → `XRelationResponseCollection` (with filters/pagination args).
|
|
66
|
+
* - v5: to-one → `X` (unwrapped), to-many → `[X]!` (unwrapped `nodes`) **and** `<field>_connection`
|
|
67
|
+
* returning `XRelationResponseCollection`.
|
|
68
|
+
*
|
|
69
|
+
* All variants use Strapi's own `buildAssociationResolver`, so query validation, output sanitizing and
|
|
70
|
+
* draft/publish handling stay identical to the generated field.
|
|
71
|
+
*
|
|
72
|
+
* Low-level building block for {@link FieldExtensionBuilder.relation} and `overrideField`; prefer those.
|
|
73
|
+
*
|
|
74
|
+
* @param resolveOverride - Replaces Strapi's association resolver. It must return Strapi's response
|
|
75
|
+
* wrapper (`{ value }` / `{ nodes }`); v5 unwrapping is applied on top.
|
|
76
|
+
* @throws Error when the content type is unknown or the attribute is not a regular relation.
|
|
77
|
+
*/
|
|
78
|
+
const relationFieldSpecs = (contentTypeUID, attributeName, resolveOverride) => {
|
|
79
|
+
var _a;
|
|
80
|
+
const contentType = getModel(contentTypeUID);
|
|
81
|
+
const attribute = (_a = contentType.attributes) === null || _a === void 0 ? void 0 : _a[attributeName];
|
|
82
|
+
if (!attribute || attribute.type !== 'relation' || !attribute.target) {
|
|
83
|
+
throw new Error(`[buro26-strapi-graphql] "${contentTypeUID}.${attributeName}" is not a regular relation attribute`);
|
|
84
|
+
}
|
|
85
|
+
const { naming } = getGraphQLService('utils');
|
|
86
|
+
const { getContentTypeArgs } = getGraphQLService('builders').utils;
|
|
87
|
+
const { buildAssociationResolver } = getGraphQLService('builders').get('content-api');
|
|
88
|
+
const targetContentType = getModel(attribute.target);
|
|
89
|
+
const isToMany = String(attribute.relation).endsWith('Many');
|
|
90
|
+
const parentTypeName = naming.getTypeName(contentType);
|
|
91
|
+
const associationResolver = resolveOverride !== null && resolveOverride !== void 0 ? resolveOverride : buildAssociationResolver({ contentTypeUID, attributeName, strapi });
|
|
92
|
+
if (!(0, exports.isStrapiV5)()) {
|
|
93
|
+
return [
|
|
94
|
+
{
|
|
95
|
+
name: attributeName,
|
|
96
|
+
type: isToMany
|
|
97
|
+
? naming.getRelationResponseCollectionName(targetContentType)
|
|
98
|
+
: naming.getEntityResponseName(targetContentType),
|
|
99
|
+
args: isToMany ? getContentTypeArgs(targetContentType) : undefined,
|
|
100
|
+
resolve: associationResolver,
|
|
101
|
+
resolverConfigPaths: [`${parentTypeName}.${attributeName}`],
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
const typeName = naming.getTypeName(targetContentType);
|
|
106
|
+
if (!isToMany) {
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
name: attributeName,
|
|
110
|
+
type: typeName,
|
|
111
|
+
resolve: async (...resolveArgs) => {
|
|
112
|
+
var _a;
|
|
113
|
+
const response = (await associationResolver(...resolveArgs));
|
|
114
|
+
return (_a = response === null || response === void 0 ? void 0 : response.value) !== null && _a !== void 0 ? _a : null;
|
|
115
|
+
},
|
|
116
|
+
resolverConfigPaths: [`${parentTypeName}.${attributeName}`],
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
const args = getContentTypeArgs(targetContentType, { isNested: true });
|
|
121
|
+
return [
|
|
122
|
+
{
|
|
123
|
+
name: `${attributeName}_connection`,
|
|
124
|
+
type: naming.getRelationResponseCollectionName(targetContentType),
|
|
125
|
+
args,
|
|
126
|
+
resolve: associationResolver,
|
|
127
|
+
resolverConfigPaths: [`${parentTypeName}.${attributeName}_connection`],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: attributeName,
|
|
131
|
+
type: (0, nexus_1.nonNull)((0, nexus_1.list)(typeName)),
|
|
132
|
+
args,
|
|
133
|
+
resolve: async (...resolveArgs) => {
|
|
134
|
+
var _a;
|
|
135
|
+
const response = (await associationResolver(...resolveArgs));
|
|
136
|
+
return (_a = response === null || response === void 0 ? void 0 : response.nodes) !== null && _a !== void 0 ? _a : [];
|
|
137
|
+
},
|
|
138
|
+
resolverConfigPaths: [`${parentTypeName}.${attributeName}`],
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
};
|
|
142
|
+
exports.relationFieldSpecs = relationFieldSpecs;
|
|
143
|
+
/**
|
|
144
|
+
* Resolver config that applies Strapi's own permission scope (`<target>.find`) to relation fields,
|
|
145
|
+
* exactly like the generated relation fields do. Pass it to an extension's `resolversConfig` when you
|
|
146
|
+
* define relation fields yourself.
|
|
147
|
+
*
|
|
148
|
+
* @returns A map of `Type.field` paths to `{ auth: { scope } }`.
|
|
149
|
+
*/
|
|
150
|
+
const relationResolversConfig = (contentTypeUID, attributeName) => {
|
|
151
|
+
var _a;
|
|
152
|
+
const attribute = (_a = getModel(contentTypeUID).attributes) === null || _a === void 0 ? void 0 : _a[attributeName];
|
|
153
|
+
const scope = [`${attribute.target}.find`];
|
|
154
|
+
return Object.fromEntries((0, exports.relationFieldSpecs)(contentTypeUID, attributeName).flatMap((spec) => spec.resolverConfigPaths.map((path) => [path, { auth: { scope } }])));
|
|
155
|
+
};
|
|
156
|
+
exports.relationResolversConfig = relationResolversConfig;
|
|
157
|
+
/**
|
|
158
|
+
* Sanitize raw database output for the content API (removes private/password fields and fields the
|
|
159
|
+
* current auth may not read). v4: `@strapi/utils` sanitize, v5: `strapi.contentAPI.sanitize`.
|
|
160
|
+
*
|
|
161
|
+
* Use it on data from {@link loaders} or `strapi.db.query()` before returning it from a resolver.
|
|
162
|
+
*
|
|
163
|
+
* @param uid - Content type of `data`.
|
|
164
|
+
* @param context - The resolver context; its `state.auth` decides what may be read.
|
|
165
|
+
*/
|
|
166
|
+
const sanitizeOutput = async (data, uid, context) => {
|
|
167
|
+
var _a;
|
|
168
|
+
if (data == null)
|
|
169
|
+
return data;
|
|
170
|
+
const schema = getModel(uid);
|
|
171
|
+
const auth = (_a = context === null || context === void 0 ? void 0 : context.state) === null || _a === void 0 ? void 0 : _a.auth;
|
|
172
|
+
if ((0, exports.isStrapiV5)()) {
|
|
173
|
+
return strapi.contentAPI.sanitize.output(data, schema, { auth });
|
|
174
|
+
}
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
176
|
+
const { sanitize } = require('@strapi/utils');
|
|
177
|
+
return sanitize.contentAPI.output(data, schema, { auth });
|
|
178
|
+
};
|
|
179
|
+
exports.sanitizeOutput = sanitizeOutput;
|
|
180
|
+
/**
|
|
181
|
+
* Wrap a single entity the way the given relation field expects: an entity response in v4, the bare
|
|
182
|
+
* entity in v5.
|
|
183
|
+
*
|
|
184
|
+
* @param targetUID - Content type of `entity`.
|
|
185
|
+
*/
|
|
186
|
+
const toRelationValue = (entity, targetUID) => {
|
|
187
|
+
if (entity == null)
|
|
188
|
+
return null;
|
|
189
|
+
if ((0, exports.isStrapiV5)())
|
|
190
|
+
return entity;
|
|
191
|
+
const { toEntityResponse } = getGraphQLService('format').returnTypes;
|
|
192
|
+
return toEntityResponse(entity, { args: {}, resourceUID: targetUID });
|
|
193
|
+
};
|
|
194
|
+
exports.toRelationValue = toRelationValue;
|
|
@@ -20,7 +20,31 @@ export type BaseUser = {
|
|
|
20
20
|
updatedAt: string;
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Project-wide type registration. Augment it once in your project:
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* declare module 'buro26-strapi-graphql' {
|
|
28
|
+
* interface Register {
|
|
29
|
+
* user: User
|
|
30
|
+
* // GraphQL type name → entity type, used to type `parent` in `.rootType('Customer')`
|
|
31
|
+
* types: { Customer: Customer; Order: Order }
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
export interface Register {
|
|
36
|
+
}
|
|
37
|
+
/** The user type registered via {@link Register}, or {@link BaseUser}. */
|
|
38
|
+
export type RegisteredUser = Register extends {
|
|
39
|
+
user: infer U;
|
|
40
|
+
} ? U : BaseUser;
|
|
41
|
+
/** GraphQL type name → entity type map registered via {@link Register}. */
|
|
42
|
+
export type RegisteredTypes = Register extends {
|
|
43
|
+
types: infer T;
|
|
44
|
+
} ? T : {};
|
|
45
|
+
/** Parent type for fields on GraphQL type `N`, when registered. */
|
|
46
|
+
export type ParentOf<N extends string, Fallback = any> = N extends keyof RegisteredTypes ? RegisteredTypes[N] : Fallback;
|
|
47
|
+
export type ResolveContext<UserType = RegisteredUser> = {
|
|
24
48
|
state: {
|
|
25
49
|
route: {
|
|
26
50
|
info: {
|