buro26-strapi-graphql 0.0.19 → 0.0.21

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 CHANGED
@@ -35,9 +35,329 @@ export default () => ({
35
35
 
36
36
  ### Create a new GraphQL Extension
37
37
 
38
+ To create a new GraphQL extension, create a folder named `graphql` in the content-type directory or within the
39
+ extensions folder (in case of extending a plugin).
40
+ Inside the `graphql` folder you can create files with any name. This plugin will scan the `graphql` folder register your
41
+ extensions with Strapi.
42
+
43
+ From each file create a default export that uses th `createGraphQLExtension` factory function. This function takes a
44
+ function as an argument that will receive the Strapi instance and should return an object with the following methods:
45
+
46
+ - init: Called when the extension is initialized
47
+ - getTypes: Returns an array of GraphQL types
48
+ - resolvers: Returns an object with resolvers
49
+ - getResolverConfig: Returns an object with resolver configuration
50
+
51
+ The functions you can implement follow the pattern of the Strapi customization
52
+ options: https://docs.strapi.io/dev-docs/plugins/graphql#customization. The return values here will be registered as-is
53
+ with Strapi in the register hook.
54
+
55
+ ```typescript
56
+ import { createGraphQLExtension } from 'buro26-strapi-graphql';
57
+ import { extendType, stringArg, nonNull } from 'nexus'
58
+
59
+ export default createGraphQLExtension(strapi => ({
60
+
61
+ init() {
62
+ // e.g. disable actions
63
+ strapi
64
+ .plugin('graphql')
65
+ .service('extension')
66
+ .shadowCRUD('api::my-type.my-type')
67
+ .disableActions(['find', 'findOne', 'create', 'update', 'delete']);
68
+ },
69
+
70
+ types() {
71
+ return [
72
+ // Extend an existing type
73
+ extendType({
74
+ type: 'MyType',
75
+ definition(t) {
76
+ t.field('foo', {
77
+ type: 'Boolean',
78
+ resolve: async (root) => {
79
+ return false;
80
+ }
81
+ });
82
+ t.field('bar', {
83
+ type: 'Boolean',
84
+ resolve: async (root) => {
85
+ return false;
86
+ }
87
+ });
88
+ }
89
+ }),
90
+
91
+ // Create a new query
92
+ extendType({
93
+ type: 'Query',
94
+ definition(t) {
95
+ t.field('helloWorld', {
96
+ type: 'HelloWorldResponse',
97
+ args: {
98
+ greeting: nonNull(stringArg())
99
+ },
100
+ // either resolve here or add it in resolvers method
101
+ resolve: async (root, args) => {
102
+ // resolve logic
103
+ }
104
+ });
105
+ }
106
+ })
107
+ ];
108
+ },
109
+
110
+ resolvers() {
111
+ return {
112
+ Query: {
113
+ // Override an existing query
114
+ helloWorld: async (root, args) => {
115
+ // resolve logic
116
+ }
117
+ },
118
+ Mutation: {
119
+ // Override an existing mutation
120
+ updateMyType: async (root, args) => {
121
+ // resolve logic
122
+ }
123
+ }
124
+ };
125
+ },
126
+
127
+ resolverConfig() {
128
+ return {
129
+ 'Query.helloWorld': {
130
+ policies: [
131
+ async ({ state, args }, { strapi }) => {
132
+ // Validate request
133
+ }
134
+ ],
135
+ middlewares: [
136
+ async (next, root, args, context, info) => {
137
+ // Do something here
138
+
139
+ return next(root, args, context, info);
140
+ }
141
+ ]
142
+ }
143
+ };
144
+ }
145
+ }));
146
+ ```
147
+
38
148
  ### Overriding a field
39
149
 
40
- ### Add authorization to a field
150
+ Sometimes you will need to override a field in an existing type. You can do this by extending the type and adding the
151
+ field again. The field will be overridden with the new field definition. To make this easier we added a helper function
152
+ `overrideField` that will make this easier. The helper method also automatically generates a resolver for the field.
153
+
154
+ The overrideField function needs the content type and fieldName to be able to resolve the field type and the resolver.
155
+ The authorize function is optional and can be used to authorize the field. The resolve function is also optional and can
156
+ be used to resolve the field.
157
+
158
+ ```typescript
159
+ import { createGraphQLExtension, overrideField } from 'buro26-strapi-graphql';
160
+ import { extendType } from 'nexus';
161
+
162
+ export default createGraphQLExtension(strapi => ({
163
+ types() {
164
+ return [
165
+ // Extend an existing type
166
+ extendType({
167
+ type: 'MyType',
168
+ definition(t) {
169
+ overrideField<MyContentType>(t, {
170
+ contentTypeName: 'api::my-type.my-type',
171
+ fieldName: 'myField',
172
+ description: 'My field description',
173
+ authorize: async (root, args, context) => {
174
+ // Authorization logic
175
+ return true;
176
+ },
177
+ resolve: async (root) => {
178
+ // Resolver is optional
179
+ return false;
180
+ }
181
+ });
182
+ }
183
+ })
184
+
185
+ ];
186
+ }
187
+ }));
188
+ ```
189
+
190
+ ### Add authorization to an existing field
191
+
192
+ By default Strapi GraphQL does not support field level authorization. This plugin adds support for field level
193
+ authorization. You can add authorization to an existing field by using the before mentioned `overrideField` function.
194
+ The authorize function will be called before the field is resolved. If the authorize function returns false the field
195
+ will not be resolved and an error will be thrown.
196
+
197
+ If you decide not to pass the resolve function and only the authorize function, the field will be resolved by the
198
+ default resolver generated by the plugin.
199
+
200
+ ```typescript
201
+ import { createGraphQLExtension, overrideField } from 'buro26-strapi-graphql';
202
+ import { extendType } from 'nexus';
203
+
204
+ export default createGraphQLExtension(strapi => ({
205
+ types() {
206
+ return [
207
+ // Extend an existing type
208
+ extendType({
209
+ type: 'MyType',
210
+ definition(t) {
211
+ overrideField<MyContentType>(t, {
212
+ contentTypeName: 'api::my-type.my-type',
213
+ fieldName: 'myField',
214
+ authorize: async (root, args, context) => {
215
+ // Decide if the user is authorized to access the field
216
+ return true;
217
+ }
218
+ });
219
+ }
220
+ })
221
+
222
+ ];
223
+ }
224
+ }));
225
+ ```
226
+
227
+ ### Utility functions
228
+
229
+ The plugin also provides utility functions to help you resolve entities and collections. These functions are useful when
230
+ you need to resolve entities and collections in your resolvers.
231
+
232
+ #### resolveEntity
233
+
234
+ The `resolveEntity` function can be used to resolve an entity by id. The function takes the content type name and an
235
+ object with all the resolve args passed the resolver function. The function will return the resolved entity as
236
+ EntityResponse.
237
+
238
+ ```typescript
239
+ import { createGraphQLExtension, overrideField, resolveEntity } from 'buro26-strapi-graphql';
240
+ import { extendType } from 'nexus';
241
+
242
+ export default createGraphQLExtension(strapi => ({
243
+ types() {
244
+ return [
245
+ // Extend an existing type
246
+ extendType({
247
+ type: 'MyType',
248
+ definition(t) {
249
+ t.field('myFields', {
250
+ type: 'MyFieldEntityResponse',
251
+ resolve: async (parent, args, context, info) => {
252
+ // Do something, and resolve the entit
253
+ return resolveEntity<MyContentType>('api::my-content-type.my-content-type', {
254
+ args: {
255
+ id: 'the id here',
256
+ },
257
+ parent,
258
+ context,
259
+ info,
260
+ })
261
+ }
262
+ });
263
+ overrideField<MyContentType>(t, {
264
+ contentTypeName: 'api::my-type.my-type',
265
+ fieldName: 'myOtherField',
266
+ resolve: async (parent, args, context, info) => {
267
+ // Do something, and resolve the entit
268
+ return resolveEntity<MyContentType>('api::my-content-type.my-content-type', {
269
+ args: {
270
+ id: 'the id here',
271
+ },
272
+ parent,
273
+ context,
274
+ info,
275
+ })
276
+ }
277
+ });
278
+ }
279
+ })
280
+
281
+ ];
282
+ }
283
+ }));
284
+ ```
285
+
286
+ #### resolveEntityCollection
287
+
288
+ The `resolveEntityCollection` function can be used to resolve a collection of entities. The function takes the content
289
+ type name and an object with all the resolve args passed the resolver function. The function will return the resolved
290
+ collection as EntityCollectionResponse.
291
+
292
+ ```typescript
293
+ import { createGraphQLExtension, overrideField, resolveEntityCollection } from 'buro26-strapi-graphql';
294
+ import { extendType } from 'nexus';
295
+
296
+ export default createGraphQLExtension(strapi => ({
297
+ types() {
298
+ return [
299
+ // Extend an existing type
300
+ extendType({
301
+ type: 'MyType',
302
+ definition(t) {
303
+ t.field('myFields', {
304
+ type: 'MyFieldEntityResponseCollection',
305
+ resolve: async (parent, args, context, info) => {
306
+ // Do something, and resolve the entit
307
+ return resolveEntityCollection<MyContentType>('api::my-content-type.my-content-type', {
308
+ args,
309
+ parent,
310
+ context,
311
+ info,
312
+ })
313
+ }
314
+ });
315
+ overrideField<MyContentType>(t, {
316
+ contentTypeName: 'api::my-type.my-type',
317
+ fieldName: 'myOtherField',
318
+ resolve: async (parent, args, context, info) => {
319
+ // Do something, and resolve the entit
320
+ return resolveEntityCollection<MyContentType>('api::my-content-type.my-content-type', {
321
+ args,
322
+ parent,
323
+ context,
324
+ info,
325
+ })
326
+ }
327
+ });
328
+ }
329
+ })
330
+
331
+ ];
332
+ }
333
+ }));
334
+ ```
335
+
336
+ #### resolveEntityRelationCollection
337
+
338
+ This function has the same API as `resolveEntityCollection` but is used to resolve a collection of entities from a
339
+ relational field on the provided content type.
340
+
341
+ #### toEntityResponse
342
+
343
+ This function is a typesafe response builder for a single entity. It takes the entity and the content type name and will
344
+ return an EntityResponse.
345
+
346
+ This function is re-exported from the Strapi GraphQL plugin.
347
+
348
+ #### toEntityCollectionResponse
349
+
350
+ This function is a typesafe response builder for a collection of entities. It takes the entities and the content type
351
+ name and will return an EntityCollectionResponse.
352
+
353
+ This function is re-exported from the Strapi GraphQL plugin.
354
+
355
+ #### resolveArgs
356
+
357
+ This function is a typesafe utility function to resolve the args passed to the resolver function. It will transform the graphql
358
+ args into args that can be passed to the entityManager.
359
+
360
+ This function is re-exported from the Strapi GraphQL plugin.
41
361
 
42
362
  ## Test and Deploy
43
363
 
@@ -1,4 +1,4 @@
1
- import { type Strapi } from '@strapi/strapi';
1
+ import type { Strapi } from '@strapi/strapi';
2
2
  declare const _default: ({ strapi }: {
3
3
  strapi: Strapi;
4
4
  }) => Promise<void>;
@@ -19,11 +19,19 @@ exports.default = async ({ strapi }) => {
19
19
  for (const extension of extensions) {
20
20
  (extension === null || extension === void 0 ? void 0 : extension.init) && extension.init();
21
21
  graphQlExtensionService.use(() => {
22
- var _a, _b, _c, _d, _e, _f;
22
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
23
23
  return ({
24
- types: (_b = (_a = extension === null || extension === void 0 ? void 0 : extension.getTypes) === null || _a === void 0 ? void 0 : _a.call(extension)) !== null && _b !== void 0 ? _b : [],
25
- resolvers: (_d = (_c = extension === null || extension === void 0 ? void 0 : extension.resolvers) === null || _c === void 0 ? void 0 : _c.call(extension)) !== null && _d !== void 0 ? _d : { Query: {}, Mutation: {} },
26
- resolversConfig: (_f = (_e = extension === null || extension === void 0 ? void 0 : extension.getResolverConfig) === null || _e === void 0 ? void 0 : _e.call(extension)) !== null && _f !== void 0 ? _f : {}
24
+ 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 : '',
25
+ types: [
26
+ ...((_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 : []),
27
+ ...((_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
+ ],
29
+ 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 : [],
30
+ 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: {} },
31
+ resolversConfig: {
32
+ ...(_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 : {},
33
+ ...(_p = (_o = extension === null || extension === void 0 ? void 0 : extension.resolverConfig) === null || _o === void 0 ? void 0 : _o.call(extension)) !== null && _p !== void 0 ? _p : {},
34
+ },
27
35
  });
28
36
  });
29
37
  }
@@ -1,5 +1,5 @@
1
- import { type Strapi } from '@strapi/strapi';
2
- import { GraphQLExtension } from '../types';
1
+ import type { Strapi } from '@strapi/strapi';
2
+ import type { GraphQLExtension } from '../types';
3
3
  declare const _default: ({ strapi }: {
4
4
  strapi: Strapi;
5
5
  }) => {
@@ -44,7 +44,9 @@ exports.default = ({ strapi }) => ({
44
44
  // Recursively get files from the directory
45
45
  return this.getAllFilesInGraphQlDirectory(filePath, collectedExtensions);
46
46
  }
47
- if (!directoryPath.endsWith('/graphql') && !directoryPath.includes('/graphql/')) {
47
+ const folderName = 'graphql';
48
+ if (!directoryPath.endsWith(path.sep + folderName) &&
49
+ !directoryPath.includes(path.sep + folderName + path.sep)) {
48
50
  return;
49
51
  }
50
52
  const graphqlModule = require(filePath.replace('.js', ''));
@@ -1,3 +1,6 @@
1
+ import type { ResolveContext } from './utils';
2
+ import type { GraphQLResolveInfo } from 'graphql';
3
+ import type { Strapi } from '@strapi/strapi';
1
4
  export interface GraphQLExtension {
2
5
  /**
3
6
  * Called when the extension is initialized, use this to perform any initialization logic
@@ -5,23 +8,69 @@ export interface GraphQLExtension {
5
8
  * @see https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#disabling-operations-in-the-shadow-crud
6
9
  */
7
10
  init?: () => void;
11
+ /**
12
+ * Return the type definitions for the extension as GraphQL SDL
13
+ */
14
+ typeDefs?: () => string;
8
15
  /**
9
16
  * Return array the types for the extension
10
17
  * Use Nexus to define types
18
+ *
19
+ * @deprecated Use types() instead
11
20
  */
12
- getTypes: () => any[];
21
+ getTypes?: () => any[];
22
+ /**
23
+ * Return array the types for the extension
24
+ * Use Nexus to define types
25
+ */
26
+ types?: () => any[];
27
+ /**
28
+ * Return optional array of nexus plugins
29
+ *
30
+ * @see https://nexusjs.org/docs/plugins
31
+ */
32
+ plugins?: () => any[];
13
33
  /**
14
34
  * Return resolvers for the extension
15
35
  * Return an object with Query and Mutation resolvers
16
36
  */
17
- resolvers: () => {
18
- Query: any;
19
- Mutation: any;
37
+ resolvers?: () => {
38
+ Query?: Record<string, ResolverFunction>;
39
+ Mutation?: Record<string, ResolverFunction>;
20
40
  };
41
+ /**
42
+ * Return resolver config for the extension
43
+ * This is used to configure middlewares and other resolver options
44
+ * @see https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#extending-the-schema
45
+ *
46
+ * @deprecated Use resolverConfig() instead
47
+ */
48
+ getResolverConfig?: () => any;
21
49
  /**
22
50
  * Return resolver config for the extension
23
51
  * This is used to configure middlewares and other resolver options
24
52
  * @see https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#extending-the-schema
25
53
  */
26
- getResolverConfig: () => any;
54
+ resolverConfig?: () => Record<string, ResolverConfigOption>;
27
55
  }
56
+ type ResolverConfigOption = {
57
+ auth?: boolean | undefined;
58
+ policies?: string[] | PolicyFunction[];
59
+ middlewares?: MiddlewareFunction[];
60
+ };
61
+ type PolicyFunctionContext = {
62
+ parent: any;
63
+ args: any;
64
+ context: ResolveContext;
65
+ info: GraphQLResolveInfo;
66
+ state: ResolveContext['state'];
67
+ http: any;
68
+ };
69
+ type PolicyFunctionOptions = {
70
+ strapi: Strapi;
71
+ };
72
+ type PolicyFunction = (context: PolicyFunctionContext, config: any, opts: PolicyFunctionOptions) => boolean | Promise<boolean>;
73
+ type MiddlewareFunction = (next: MiddlewareNextFunction, parent: any, args: any, context: ResolveContext, info: GraphQLResolveInfo) => any | Promise<any>;
74
+ type MiddlewareNextFunction = (parent: any, args: any, context: ResolveContext, info: GraphQLResolveInfo) => Promise<any>;
75
+ type ResolverFunction = (parent: any, args: any, context: ResolveContext, info: GraphQLResolveInfo) => any | Promise<any>;
76
+ export {};
@@ -6,3 +6,15 @@
6
6
  import { Entity, type ResolveArgs } from './types';
7
7
  export declare const resolveEntityCollection: <T extends Object>(contentType: string, resolveArgs: ResolveArgs) => Promise<Entity<T>[]>;
8
8
  export declare const resolveEntityRelationCollection: <T extends Object>(contentType: string, relationFieldName: keyof T, resolveArgs: ResolveArgs) => Promise<Entity<T>[]>;
9
+ type EntityResponse<T extends Object> = {
10
+ value: Entity<T> | null;
11
+ info: {
12
+ args: Record<string, any>;
13
+ resourceUID: string;
14
+ };
15
+ };
16
+ export default function toEntityResponseCollection<T extends Object>(items: T[], { args, resourceUID }: {
17
+ args: Record<string, any>;
18
+ resourceUID: string;
19
+ }): EntityResponse<T>[];
20
+ export {};
@@ -33,3 +33,11 @@ const resolveEntityRelationCollection = async (contentType, relationFieldName, r
33
33
  return find(resolveArgs.parent, resolveArgs.args, resolveArgs.context);
34
34
  };
35
35
  exports.resolveEntityRelationCollection = resolveEntityRelationCollection;
36
+ function toEntityResponseCollection(items, { args, resourceUID }) {
37
+ const { toEntityResponseCollection } = strapi.plugin('graphql').service('format').returnTypes;
38
+ return toEntityResponseCollection(items, {
39
+ args,
40
+ resourceUID
41
+ });
42
+ }
43
+ exports.default = toEntityResponseCollection;
@@ -1,4 +1,5 @@
1
1
  export * from './types';
2
2
  export * from './single-type';
3
3
  export * from './collection-type';
4
+ export * from './misc';
4
5
  export * from './override';
@@ -17,4 +17,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./types"), exports);
18
18
  __exportStar(require("./single-type"), exports);
19
19
  __exportStar(require("./collection-type"), exports);
20
+ __exportStar(require("./misc"), exports);
20
21
  __exportStar(require("./override"), exports);
@@ -0,0 +1,4 @@
1
+ export declare function transformArgs(args: Record<string, any>, { contentType, usePagination }: {
2
+ contentType: string;
3
+ usePagination?: boolean;
4
+ }): Record<string, any>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformArgs = void 0;
4
+ function transformArgs(args, { contentType, usePagination }) {
5
+ const { transformArgs } = strapi.plugin('graphql').service('builders').utils;
6
+ return transformArgs(args, {
7
+ contentType,
8
+ usePagination: usePagination !== null && usePagination !== void 0 ? usePagination : true
9
+ });
10
+ }
11
+ exports.transformArgs = transformArgs;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Easily resolve a entity
2
+ * Easily resolve an entity
3
3
  *
4
4
  * Based on node_modules/@strapi/plugin-graphql/server/services/builders/type.js
5
5
  */
@@ -9,5 +9,16 @@ type ResolveEntityArgs = Omit<ResolveArgs, 'args'> & {
9
9
  id: string;
10
10
  };
11
11
  };
12
- export declare const resolveEntity: <T extends Object>(contentType: string, resolveArgs: ResolveEntityArgs) => Promise<Entity<T>>;
12
+ export declare function resolveEntity<T extends Object>(contentType: string, resolveArgs: ResolveEntityArgs): Promise<Entity<T> | null>;
13
+ type EntityResponse<T extends Object> = {
14
+ value: Entity<T> | null;
15
+ info: {
16
+ args: Record<string, any>;
17
+ resourceUID: string;
18
+ };
19
+ };
20
+ export declare function toEntityResponse<T extends Object>(item: T | null, { args, resourceUID }: {
21
+ args: Record<string, any>;
22
+ resourceUID: string;
23
+ }): Promise<EntityResponse<T> | null>;
13
24
  export {};
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  /**
3
- * Easily resolve a entity
3
+ * Easily resolve an entity
4
4
  *
5
5
  * Based on node_modules/@strapi/plugin-graphql/server/services/builders/type.js
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.resolveEntity = void 0;
9
- const resolveEntity = async (contentType, resolveArgs) => {
8
+ exports.toEntityResponse = exports.resolveEntity = void 0;
9
+ async function resolveEntity(contentType, resolveArgs) {
10
10
  const { transformArgs } = strapi.plugin('graphql').service('builders').utils;
11
11
  const { toEntityResponse } = strapi.plugin('graphql').service('format').returnTypes;
12
12
  const { buildQueriesResolvers } = strapi.plugin('graphql').service('builders').get('content-api');
@@ -15,7 +15,15 @@ const resolveEntity = async (contentType, resolveArgs) => {
15
15
  const item = await findOne(resolveArgs.parent, transformedArgs, resolveArgs.context);
16
16
  return toEntityResponse(item, {
17
17
  args: resolveArgs.args,
18
- resourceUID: contentType,
18
+ resourceUID: contentType
19
19
  });
20
- };
20
+ }
21
21
  exports.resolveEntity = resolveEntity;
22
+ async function toEntityResponse(item, { args, resourceUID }) {
23
+ const { toEntityResponse } = strapi.plugin('graphql').service('format').returnTypes;
24
+ return toEntityResponse(item, {
25
+ args,
26
+ resourceUID
27
+ });
28
+ }
29
+ exports.toEntityResponse = toEntityResponse;