chore(graphql): migration to basic ts (#18696)

This commit is contained in:
Alexandre BODIN 2023-11-08 18:21:41 +01:00 committed by GitHub
parent 6129ef3c6c
commit 5c5ae949bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
148 changed files with 1425 additions and 1197 deletions

View File

@ -3,6 +3,7 @@ import type { Utils } from '../..';
export interface EnumerationProperties<TValues extends string[] = []> {
enum: TValues;
enumName?: string;
}
export type Enumeration<TValues extends string[] = []> = Attribute.OfType<'enumeration'> &

View File

@ -16,9 +16,9 @@ export type RouteConfig = {
};
export type Route = {
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'ALL' | 'OPTIONS' | 'HEAD';
path: string;
handler: string | MiddlewareHandler;
handler: string | MiddlewareHandler | MiddlewareHandler[];
info: RouteInfo;
config?: RouteConfig;
};

View File

@ -146,3 +146,5 @@ export interface Component extends Schema {
category: string;
}
export type Any = SingleType | CollectionType | Component;

View File

@ -1 +1 @@
dist
dist

View File

@ -2,12 +2,8 @@
"root": true,
"overrides": [
{
"files": ["admin/**/*"],
"extends": ["custom/front"]
},
{
"files": ["**/*"],
"excludedFiles": ["admin/**/*"],
"files": ["**"],
"excludedFiles": ["admin/**/*", "server/**/*"],
"extends": ["custom/back"]
}
]

View File

@ -11,3 +11,5 @@ npm-debug.log
.idea
generated
dist/

View File

@ -0,0 +1,4 @@
{
"root": true,
"extends": ["custom/front/typescript"]
}

View File

@ -2,19 +2,21 @@ import { prefixPluginTranslations } from '@strapi/helper-plugin';
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import { pluginId } from './pluginId';
const name = pluginPkg.strapi.name;
// eslint-disable-next-line import/no-default-export
export default {
register(app) {
// TODO: we need to have the type for StrapiApp done from `@strapi/admin` package.
register(app: any) {
app.registerPlugin({
id: pluginId,
name,
});
},
bootstrap() {},
async registerTrads({ locales }) {
async registerTrads({ locales }: { locales: string[] }) {
const importedTrads = await Promise.all(
locales.map((locale) => {
return import(`./translations/${locale}.json`)

View File

@ -1,5 +0,0 @@
import pluginPkg from '../../package.json';
const pluginId = pluginPkg.name.replace(/^@strapi\/plugin-/i, '');
export default pluginId;

View File

@ -0,0 +1 @@
export const pluginId = 'graphql';

View File

@ -0,0 +1,8 @@
{
"extends": "./admin/tsconfig.json",
"include": ["./admin/src"],
"exclude": ["**/*.test.tsx"],
"compilerOptions": {
"outDir": "./dist/admin"
}
}

View File

@ -0,0 +1,5 @@
{
"extends": "tsconfig/client.json",
"include": ["./src"],
"exclude": ["node_modules"]
}

View File

@ -22,23 +22,31 @@
],
"exports": {
"./strapi-admin": {
"source": "./admin/src/index.js",
"types": "./dist/admin/index.d.ts",
"source": "./admin/src/index.ts",
"import": "./dist/admin/index.mjs",
"require": "./dist/admin/index.js",
"default": "./dist/admin/index.js"
},
"./strapi-server": {
"source": "./strapi-server.js",
"require": "./strapi-server.js",
"default": "./strapi-server.js"
"types": "./dist/server/index.d.ts",
"source": "./server/src/index.ts",
"import": "./dist/server/index.mjs",
"require": "./dist/server/index.js",
"default": "./dist/server/index.js"
},
"./package.json": "./package.json"
},
"files": [
"./dist",
"strapi-server.js"
],
"scripts": {
"build": "pack-up build",
"clean": "run -T rimraf dist",
"build": "strapi plugin:build --force",
"clean": "run -T rimraf ./dist",
"lint": "run -T eslint .",
"watch": "pack-up watch"
"prepublishOnly": "yarn clean && yarn build",
"watch": "strapi plugin:watch"
},
"dependencies": {
"@graphql-tools/schema": "8.5.1",
@ -60,14 +68,19 @@
"pluralize": "8.0.0"
},
"devDependencies": {
"@strapi/pack-up": "workspace:*",
"@strapi/strapi": "4.15.1",
"@strapi/types": "4.15.1",
"@types/graphql-depth-limit": "1.1.5",
"@types/graphql-upload": "8.0.12",
"cross-env": "^7.0.3",
"eslint-config-custom": "4.15.1",
"koa": "2.13.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "5.3.4",
"styled-components": "5.3.3"
"styled-components": "5.3.3",
"tsconfig": "4.15.1",
"typescript": "5.2.2"
},
"peerDependencies": {
"@strapi/strapi": "^4.0.0",

View File

@ -1,42 +0,0 @@
import { Config, defineConfig } from '@strapi/pack-up';
import { transformWithEsbuild } from 'vite';
const config: Config = defineConfig({
bundles: [
{
source: './admin/src/index.js',
import: './dist/admin/index.mjs',
require: './dist/admin/index.js',
runtime: 'web',
},
],
dist: './dist',
/**
* Because we're exporting a server & client package
* which have different runtimes we want to ignore
* what they look like in the package.json
*/
exports: {},
plugins: [
{
name: 'treat-js-files-as-jsx',
async transform(code, id) {
/**
* Matches all files in src/ and ee/ that end with .js
*/
if (!id.match(/src\/.*\.js$/) && !id.match(/ee\/.*\.js$/)) {
return null;
}
// Use the exposed transform from vite, instead of directly
// transforming with esbuild
return transformWithEsbuild(code, id, {
loader: 'tsx',
jsx: 'automatic',
});
},
},
],
});
export default config;

View File

@ -0,0 +1,7 @@
{
"root": true,
"extends": ["custom/back/typescript"],
"parserOptions": {
"project": ["./server/tsconfig.eslint.json"]
}
}

View File

@ -1,7 +0,0 @@
'use strict';
const defaultConfig = require('./default-config');
module.exports = {
default: defaultConfig,
};

View File

@ -1,7 +0,0 @@
'use strict';
const contentType = require('./content-type');
module.exports = (context) => ({
...contentType(context),
});

View File

@ -1,15 +0,0 @@
'use strict';
const { list } = require('nexus');
const AND_FIELD_NAME = 'and';
module.exports = () => ({
fieldName: AND_FIELD_NAME,
strapiOperator: '$and',
add(t, type) {
t.field(AND_FIELD_NAME, { type: list(type) });
},
});

View File

@ -1,15 +0,0 @@
'use strict';
const { list } = require('nexus');
const IN_FIELD_NAME = 'in';
module.exports = () => ({
fieldName: IN_FIELD_NAME,
strapiOperator: '$in',
add(t, type) {
t.field(IN_FIELD_NAME, { type: list(type) });
},
});

View File

@ -1,40 +0,0 @@
'use strict';
const { mapValues } = require('lodash/fp');
const operators = {
and: require('./and'),
or: require('./or'),
not: require('./not'),
eq: require('./eq'),
eqi: require('./eqi'),
ne: require('./ne'),
nei: require('./nei'),
startsWith: require('./starts-with'),
endsWith: require('./ends-with'),
contains: require('./contains'),
notContains: require('./not-contains'),
containsi: require('./containsi'),
notContainsi: require('./not-containsi'),
gt: require('./gt'),
gte: require('./gte'),
lt: require('./lt'),
lte: require('./lte'),
null: require('./null'),
notNull: require('./not-null'),
in: require('./in'),
notIn: require('./not-in'),
between: require('./between'),
};
// Instantiate every operator with the Strapi instance
module.exports = (context) => mapValues((opCtor) => opCtor(context), operators);

View File

@ -1,15 +0,0 @@
'use strict';
const { list } = require('nexus');
const OR_FIELD_NAME = 'or';
module.exports = () => ({
fieldName: OR_FIELD_NAME,
strapiOperator: '$or',
add(t, type) {
t.field(OR_FIELD_NAME, { type: list(type) });
},
});

View File

@ -1,9 +0,0 @@
'use strict';
const createCollectionTypeMutationsBuilder = require('./collection-type');
const createSingleTypeMutationsBuilder = require('./single-type');
module.exports = (context) => ({
...createCollectionTypeMutationsBuilder(context),
...createSingleTypeMutationsBuilder(context),
});

View File

@ -1,9 +0,0 @@
'use strict';
const createCollectionTypeQueriesBuilder = require('./collection-type');
const createSingleTypeQueriesBuilder = require('./single-type');
module.exports = (context) => ({
...createCollectionTypeQueriesBuilder(context),
...createSingleTypeQueriesBuilder(context),
});

View File

@ -1,9 +0,0 @@
'use strict';
module.exports = ({ strapi }) => ({
buildDynamicZoneResolver({ contentTypeUID, attributeName }) {
return async (parent) => {
return strapi.entityService.load(contentTypeUID, parent, attributeName);
};
},
});

View File

@ -1,18 +0,0 @@
'use strict';
const associationResolvers = require('./association');
const queriesResolvers = require('./query');
const mutationsResolvers = require('./mutation');
const componentResolvers = require('./component');
const dynamicZoneResolvers = require('./dynamic-zone');
module.exports = (context) => ({
// Generics
...associationResolvers(context),
// Builders
...mutationsResolvers(context),
...queriesResolvers(context),
...componentResolvers(context),
...dynamicZoneResolvers(context),
});

View File

@ -1,34 +0,0 @@
'use strict';
const { omit } = require('lodash/fp');
const { sanitize, validate } = require('@strapi/utils');
module.exports = ({ strapi }) => ({
buildQueriesResolvers({ contentType }) {
const { uid } = contentType;
return {
async find(parent, args, ctx) {
await validate.contentAPI.query(args, contentType, {
auth: ctx?.state?.auth,
});
const sanitizedQuery = await sanitize.contentAPI.query(args, contentType, {
auth: ctx?.state?.auth,
});
return strapi.entityService.findMany(uid, sanitizedQuery);
},
async findOne(parent, args, ctx) {
await validate.contentAPI.query(args, contentType, {
auth: ctx?.state?.auth,
});
const sanitizedQuery = await sanitize.contentAPI.query(args, contentType, {
auth: ctx?.state?.auth,
});
return strapi.entityService.findOne(uid, args.id, omit('id', sanitizedQuery));
},
};
},
});

View File

@ -1,60 +0,0 @@
'use strict';
const { propOr } = require('lodash/fp');
const { policy: policyUtils } = require('@strapi/utils');
const { PolicyError } = require('@strapi/utils').errors;
const getPoliciesConfig = propOr([], 'policies');
const createPoliciesMiddleware = (resolverConfig, { strapi }) => {
const resolverPolicies = getPoliciesConfig(resolverConfig);
const policies = policyUtils.resolve(resolverPolicies);
return async (resolve, ...rest) => {
// Create a graphql policy context
const context = createGraphQLPolicyContext(...rest);
// Run policies & throw an error if one of them fails
for (const { handler, config } of policies) {
const result = await handler(context, config, { strapi });
if (![true, undefined].includes(result)) {
throw new PolicyError();
}
}
return resolve(...rest);
};
};
const createGraphQLPolicyContext = (parent, args, context, info) => {
return policyUtils.createPolicyContext('graphql', {
get parent() {
return parent;
},
get args() {
return args;
},
get context() {
return context;
},
get info() {
return info;
},
get state() {
return this.context.state;
},
get http() {
return this.context.koaContext;
},
});
};
module.exports = {
createPoliciesMiddleware,
};

View File

@ -1,13 +0,0 @@
'use strict';
const { registerDynamicZonesDefinition } = require('./dynamic-zones');
const { registerEnumsDefinition } = require('./enums');
const { registerInputsDefinition } = require('./inputs');
const { registerFiltersDefinition } = require('./filters');
module.exports = {
registerDynamicZonesDefinition,
registerFiltersDefinition,
registerInputsDefinition,
registerEnumsDefinition,
};

View File

@ -1,22 +0,0 @@
'use strict';
const { registerCollectionType } = require('./collection-type');
const { registerSingleType } = require('./single-type');
const { registerComponent } = require('./component');
const { registerPolymorphicContentType } = require('./polymorphic');
const { registerScalars } = require('./scalars');
const { registerInternals } = require('./internals');
const contentType = require('./content-type');
module.exports = {
registerCollectionType,
registerSingleType,
registerComponent,
registerPolymorphicContentType,
registerInternals,
registerScalars,
contentType,
};

View File

@ -1,5 +0,0 @@
'use strict';
const createExtension = require('./extension');
module.exports = createExtension;

View File

@ -1,7 +0,0 @@
'use strict';
const returnTypes = require('./return-types');
module.exports = (context) => ({
returnTypes: returnTypes(context),
});

View File

@ -1,27 +0,0 @@
'use strict';
module.exports = () => ({
/**
* @param {object} value
* @param {object} info
* @param {object} info.args
* @param {string} info.resourceUID
*/
toEntityResponse(value, info = {}) {
const { args = {}, resourceUID } = info;
return { value, info: { args, resourceUID } };
},
/**
* @param {object[]} nodes
* @param {object} info
* @param {object} info.args
* @param {string} info.resourceUID
*/
toEntityResponseCollection(nodes, info = {}) {
const { args = {}, resourceUID } = info;
return { nodes, info: { args, resourceUID } };
},
});

View File

@ -1,21 +0,0 @@
'use strict';
const contentAPI = require('./content-api');
const typeRegistry = require('./type-registry');
const utils = require('./utils');
const constants = require('./constants');
const internals = require('./internals');
const builders = require('./builders');
const extension = require('./extension');
const format = require('./format');
module.exports = {
builders,
'content-api': contentAPI,
constants,
extension,
format,
internals,
'type-registry': typeRegistry,
utils,
};

View File

@ -1,11 +0,0 @@
'use strict';
const SortArg = require('./sort');
const publicationState = require('./publication-state');
const PaginationArg = require('./pagination');
module.exports = (context) => ({
SortArg,
PaginationArg,
PublicationStateArg: publicationState(context),
});

View File

@ -1,10 +0,0 @@
'use strict';
const { arg, list } = require('nexus');
const SortArg = arg({
type: list('String'),
default: [],
});
module.exports = SortArg;

View File

@ -1,7 +0,0 @@
'use strict';
const getEnabledScalars = require('./get-enabled-scalars');
module.exports = (context) => ({
getEnabledScalars: getEnabledScalars(context),
});

View File

@ -1,13 +0,0 @@
'use strict';
const args = require('./args');
const scalars = require('./scalars');
const types = require('./types');
const helpers = require('./helpers');
module.exports = (context) => ({
args: args(context),
scalars: scalars(context),
buildInternalTypes: types(context),
helpers: helpers(context),
});

View File

@ -1,17 +0,0 @@
'use strict';
const { GraphQLDateTime, GraphQLLong, GraphQLJSON } = require('graphql-scalars');
const { GraphQLUpload } = require('graphql-upload');
const { asNexusMethod } = require('nexus');
const TimeScalar = require('./time');
const GraphQLDate = require('./date');
module.exports = () => ({
JSON: asNexusMethod(GraphQLJSON, 'json'),
DateTime: asNexusMethod(GraphQLDateTime, 'dateTime'),
Time: asNexusMethod(TimeScalar, 'time'),
Date: asNexusMethod(GraphQLDate, 'date'),
Long: asNexusMethod(GraphQLLong, 'long'),
Upload: asNexusMethod(GraphQLUpload, 'upload'),
});

View File

@ -1,11 +0,0 @@
'use strict';
const mappers = require('./mappers');
const attributes = require('./attributes');
const naming = require('./naming');
module.exports = (context) => ({
naming: naming(context),
attributes: attributes(context),
mappers: mappers(context),
});

View File

@ -1,12 +0,0 @@
'use strict';
const { map } = require('lodash/fp');
const entityToResponseEntity = (entity) => ({ id: entity.id, attributes: entity });
const entitiesToResponseEntities = map(entityToResponseEntity);
module.exports = () => ({
entityToResponseEntity,
entitiesToResponseEntities,
});

View File

@ -1,13 +0,0 @@
'use strict';
const strapiScalarToGraphQLScalar = require('./strapi-scalar-to-graphql-scalar');
const graphQLFiltersToStrapiQuery = require('./graphql-filters-to-strapi-query');
const graphqlScalarToOperators = require('./graphql-scalar-to-operators');
const entityToResponseEntity = require('./entity-to-response-entity');
module.exports = (context) => ({
...strapiScalarToGraphQLScalar(context),
...graphQLFiltersToStrapiQuery(context),
...graphqlScalarToOperators(context),
...entityToResponseEntity(context),
});

View File

@ -1,14 +1,15 @@
'use strict';
const { isEmpty, mergeWith, isArray } = require('lodash/fp');
const { ApolloServer } = require('apollo-server-koa');
const {
import { isEmpty, mergeWith, isArray } from 'lodash/fp';
import { ApolloServer } from 'apollo-server-koa';
import {
ApolloServerPluginLandingPageDisabled,
ApolloServerPluginLandingPageGraphQLPlayground,
} = require('apollo-server-core');
const depthLimit = require('graphql-depth-limit');
const { graphqlUploadKoa } = require('graphql-upload');
const formatGraphqlError = require('./format-graphql-error');
} from 'apollo-server-core';
import depthLimit from 'graphql-depth-limit';
import { graphqlUploadKoa } from 'graphql-upload';
import type { Config } from 'apollo-server-core';
import type { Strapi } from '@strapi/types';
import { formatGraphqlError } from './format-graphql-error';
const merge = mergeWith((a, b) => {
if (isArray(a) && isArray(b)) {
@ -21,7 +22,7 @@ const merge = mergeWith((a, b) => {
* @param {object} strapi
* @param {string} path
*/
const useUploadMiddleware = (strapi, path) => {
const useUploadMiddleware = (strapi: Strapi, path: string): void => {
const uploadMiddleware = graphqlUploadKoa();
strapi.server.app.use((ctx, next) => {
@ -33,7 +34,7 @@ const useUploadMiddleware = (strapi, path) => {
});
};
module.exports = async ({ strapi }) => {
export async function bootstrap({ strapi }: { strapi: Strapi }) {
// Generate the GraphQL schema for the content API
const schema = strapi.plugin('graphql').service('content-api').buildSchema();
@ -45,9 +46,13 @@ module.exports = async ({ strapi }) => {
const { config } = strapi.plugin('graphql');
const path = config('endpoint');
const path: string = config('endpoint');
const defaultServerConfig = {
const defaultServerConfig: Config & {
cors: boolean;
uploads: boolean;
bodyParserConfig: boolean;
} = {
// Schema
schema,
@ -58,7 +63,7 @@ module.exports = async ({ strapi }) => {
}),
// Validation
validationRules: [depthLimit(config('depthLimit'))],
validationRules: [depthLimit(config('depthLimit') as number) as any],
// Errors
formatError: formatGraphqlError,
@ -74,7 +79,7 @@ module.exports = async ({ strapi }) => {
: ApolloServerPluginLandingPageGraphQLPlayground(),
],
cache: 'bounded',
cache: 'bounded' as const,
};
const serverConfig = merge(defaultServerConfig, config('apolloServer'));
@ -88,8 +93,12 @@ module.exports = async ({ strapi }) => {
try {
// Since Apollo-Server v3, server.start() must be called before using server.applyMiddleware()
await server.start();
} catch (e) {
strapi.log.error('Failed to start the Apollo server', e.message);
} catch (error) {
if (error instanceof Error) {
strapi.log.error('Failed to start the Apollo server', error.message);
}
throw error;
}
// Link the Apollo server & the Strapi app
@ -131,4 +140,4 @@ module.exports = async ({ strapi }) => {
strapi.plugin('graphql').destroy = async () => {
await server.stop();
};
};
}

View File

@ -1,13 +1,7 @@
'use strict';
module.exports = {
export default {
shadowCRUD: true,
endpoint: '/graphql',
subscriptions: false,
maxLimit: -1,
apolloServer: {},
};

View File

@ -0,0 +1,5 @@
import defaultConfig from './default-config';
export const config = {
default: defaultConfig,
};

View File

@ -1,19 +1,20 @@
'use strict';
const { toUpper, snakeCase, pick, isEmpty } = require('lodash/fp');
const { HttpError, ForbiddenError, UnauthorizedError, ApplicationError, ValidationError } =
require('@strapi/utils').errors;
const {
import { toUpper, snakeCase, pick, isEmpty } from 'lodash/fp';
import { errors } from '@strapi/utils';
import {
ApolloError,
UserInputError: ApolloUserInputError,
ForbiddenError: ApolloForbiddenError,
} = require('apollo-server-koa');
const { GraphQLError } = require('graphql');
UserInputError as ApolloUserInputError,
ForbiddenError as ApolloForbiddenError,
} from 'apollo-server-koa';
import { GraphQLError } from 'graphql';
const formatToCode = (name) => `STRAPI_${toUpper(snakeCase(name))}`;
const formatErrorToExtension = (error) => ({ error: pick(['name', 'message', 'details'])(error) });
const { HttpError, ForbiddenError, UnauthorizedError, ApplicationError, ValidationError } = errors;
const formatGraphqlError = (error) => {
const formatToCode = (name: string) => `STRAPI_${toUpper(snakeCase(name))}`;
const formatErrorToExtension = (error: any) => ({
error: pick(['name', 'message', 'details'])(error),
});
export function formatGraphqlError(error: GraphQLError) {
const { originalError } = error;
if (isEmpty(originalError)) {
@ -40,6 +41,4 @@ const formatGraphqlError = (error) => {
// Internal server error
strapi.log.error(originalError);
return new ApolloError('Internal Server Error', 'INTERNAL_SERVER_ERROR');
};
module.exports = formatGraphqlError;
}

View File

@ -0,0 +1,9 @@
import { config } from './config';
import { bootstrap } from './bootstrap';
import { services } from './services';
export default {
config,
bootstrap,
services,
};

View File

@ -1,12 +1,15 @@
'use strict';
import { Kind, valueFromASTUntyped } from 'graphql';
import { omit } from 'lodash/fp';
import { unionType, scalarType } from 'nexus';
import { errors } from '@strapi/utils';
import type { UID, Attribute } from '@strapi/types';
const { Kind, valueFromASTUntyped } = require('graphql');
const { omit } = require('lodash/fp');
const { unionType, scalarType } = require('nexus');
const { ApplicationError } = require('@strapi/utils').errors;
import type { Context } from '../types';
module.exports = ({ strapi }) => {
const buildTypeDefinition = (name, components) => {
const { ApplicationError } = errors;
export default ({ strapi }: Context) => {
const buildTypeDefinition = (name: string, components: UID.Component[]) => {
const { ERROR_TYPE_NAME } = strapi.plugin('graphql').service('constants');
const isEmpty = components.length === 0;
@ -39,8 +42,8 @@ module.exports = ({ strapi }) => {
});
};
const buildInputDefinition = (name, components) => {
const parseData = (value) => {
const buildInputDefinition = (name: string, components: UID.Component[]) => {
const parseData = (value: any) => {
const component = Object.values(strapi.components).find(
(component) => component.globalId === value.__typename
);
@ -85,7 +88,7 @@ module.exports = ({ strapi }) => {
* @param {string} inputName - the name of the dynamic zone's input
* @return {[NexusUnionTypeDef, NexusScalarTypeDef]}
*/
buildDynamicZoneDefinition(definition, name, inputName) {
buildDynamicZoneDefinition(definition: Attribute.DynamicZone, name: string, inputName: string) {
const { components } = definition;
const typeDefinition = buildTypeDefinition(name, components);

View File

@ -1,7 +1,5 @@
'use strict';
function buildEntityMetaDefinition(/* contentType */) {}
module.exports = () => ({
export default () => ({
buildEntityMetaDefinition,
});

View File

@ -1,9 +1,10 @@
'use strict';
import { objectType } from 'nexus';
import { prop, identity, isEmpty } from 'lodash/fp';
import type { Schema } from '@strapi/types';
const { objectType } = require('nexus');
const { prop, identity, isEmpty } = require('lodash/fp');
import type { Context } from '../types';
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const { naming } = strapi.plugin('graphql').service('utils');
return {
@ -12,7 +13,7 @@ module.exports = ({ strapi }) => {
* @param {object} contentType The content type which will be used to build its entity type
* @return {NexusObjectTypeDef}
*/
buildEntityDefinition(contentType) {
buildEntityDefinition(contentType: Schema.ContentType) {
const { attributes } = contentType;
const name = naming.getEntityName(contentType);

View File

@ -1,8 +1,10 @@
'use strict';
import { enumType } from 'nexus';
import { set } from 'lodash/fp';
import { toRegressedEnumValue } from '@strapi/utils';
const { enumType } = require('nexus');
const { set } = require('lodash/fp');
const { toRegressedEnumValue } = require('@strapi/utils');
interface Definition {
enum: string[];
}
/**
* Build a Nexus enum type from a Strapi enum attribute
@ -11,7 +13,7 @@ const { toRegressedEnumValue } = require('@strapi/utils');
* @param {string} name - The name of the enum
* @return {NexusEnumTypeDef}
*/
const buildEnumTypeDefinition = (definition, name) => {
const buildEnumTypeDefinition = (definition: Definition, name: string) => {
return enumType({
name,
members: definition.enum.reduce(
@ -21,6 +23,6 @@ const buildEnumTypeDefinition = (definition, name) => {
});
};
module.exports = () => ({
export default () => ({
buildEnumTypeDefinition,
});

View File

@ -1,15 +1,20 @@
'use strict';
import { inputObjectType } from 'nexus';
import type * as Nexus from 'nexus';
import type { Attribute, Schema } from '@strapi/types';
import type { Context } from '../../types';
const { inputObjectType } = require('nexus');
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const rootLevelOperators = () => {
const { operators } = strapi.plugin('graphql').service('builders').filters;
return [operators.and, operators.or, operators.not];
};
const addScalarAttribute = (builder, attributeName, attribute) => {
const addScalarAttribute = (
builder: Nexus.blocks.InputDefinitionBlock<any>,
attributeName: string,
attribute: Attribute.Any
) => {
const { naming, mappers } = strapi.plugin('graphql').service('utils');
const gqlType = mappers.strapiScalarToGraphQLScalar(attribute.type);
@ -17,13 +22,17 @@ module.exports = ({ strapi }) => {
builder.field(attributeName, { type: naming.getScalarFilterInputTypeName(gqlType) });
};
const addRelationalAttribute = (builder, attributeName, attribute) => {
const addRelationalAttribute = (
builder: Nexus.blocks.InputDefinitionBlock<any>,
attributeName: string,
attribute: Attribute.Relation
) => {
const utils = strapi.plugin('graphql').service('utils');
const extension = strapi.plugin('graphql').service('extension');
const { getFiltersInputTypeName } = utils.naming;
const { isMorphRelation } = utils.attributes;
const model = strapi.getModel(attribute.target);
const model = 'target' in attribute && strapi.getModel(attribute.target);
// If there is no model corresponding to the attribute configuration
// or if the attribute is a polymorphic relation, then ignore it
@ -35,7 +44,11 @@ module.exports = ({ strapi }) => {
builder.field(attributeName, { type: getFiltersInputTypeName(model) });
};
const addComponentAttribute = (builder, attributeName, attribute) => {
const addComponentAttribute = (
builder: Nexus.blocks.InputDefinitionBlock<any>,
attributeName: string,
attribute: Attribute.Component
) => {
const utils = strapi.plugin('graphql').service('utils');
const extension = strapi.plugin('graphql').service('extension');
const { getFiltersInputTypeName } = utils.naming;
@ -51,7 +64,7 @@ module.exports = ({ strapi }) => {
builder.field(attributeName, { type: getFiltersInputTypeName(component) });
};
const buildContentTypeFilters = (contentType) => {
const buildContentTypeFilters = (contentType: Schema.ContentType) => {
const utils = strapi.plugin('graphql').service('utils');
const extension = strapi.plugin('graphql').service('extension');
@ -88,12 +101,12 @@ module.exports = ({ strapi }) => {
// Handle relations
else if (isRelation(attribute)) {
addRelationalAttribute(t, attributeName, attribute);
addRelationalAttribute(t, attributeName, attribute as Attribute.Relation);
}
// Handle components
else if (isComponent(attribute)) {
addComponentAttribute(t, attributeName, attribute);
addComponentAttribute(t, attributeName, attribute as Attribute.Component);
}
}

View File

@ -0,0 +1,6 @@
import contentType from './content-type';
import type { Context } from '../../types';
export default (context: Context) => ({
...contentType(context),
});

View File

@ -0,0 +1,14 @@
import { list } from 'nexus';
import type * as Nexus from 'nexus';
const AND_FIELD_NAME = 'and';
export default () => ({
fieldName: AND_FIELD_NAME,
strapiOperator: '$and',
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(AND_FIELD_NAME, { type: list(type) });
},
});

View File

@ -1,15 +1,14 @@
'use strict';
const { list } = require('nexus');
import { list } from 'nexus';
import type * as Nexus from 'nexus';
const BETWEEN_FIELD_NAME = 'between';
module.exports = () => ({
export default () => ({
fieldName: BETWEEN_FIELD_NAME,
strapiOperator: '$between',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(BETWEEN_FIELD_NAME, { type: list(type) });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const CONTAINS_FIELD_NAME = 'contains';
module.exports = () => ({
export default () => ({
fieldName: CONTAINS_FIELD_NAME,
strapiOperator: '$contains',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(CONTAINS_FIELD_NAME, { type });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const CONTAINSI_FIELD_NAME = 'containsi';
module.exports = () => ({
export default () => ({
fieldName: CONTAINSI_FIELD_NAME,
strapiOperator: '$containsi',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(CONTAINSI_FIELD_NAME, { type });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const ENDS_WITH_FIELD_NAME = 'endsWith';
module.exports = () => ({
export default () => ({
fieldName: ENDS_WITH_FIELD_NAME,
strapiOperator: '$endsWith',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(ENDS_WITH_FIELD_NAME, { type });
},
});

View File

@ -1,15 +1,17 @@
'use strict';
import { errors } from '@strapi/utils';
import type * as Nexus from 'nexus';
import type { Strapi } from '@strapi/types';
const { ValidationError } = require('@strapi/utils').errors;
const { ValidationError } = errors;
const EQ_FIELD_NAME = 'eq';
module.exports = ({ strapi }) => ({
export default ({ strapi }: { strapi: Strapi }) => ({
fieldName: EQ_FIELD_NAME,
strapiOperator: '$eq',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
const { GRAPHQL_SCALARS } = strapi.plugin('graphql').service('constants');
if (!GRAPHQL_SCALARS.includes(type)) {

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const EQI_FIELD_NAME = 'eqi';
module.exports = () => ({
export default () => ({
fieldName: EQI_FIELD_NAME,
strapiOperator: '$eqi',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(EQI_FIELD_NAME, { type });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const GT_FIELD_NAME = 'gt';
module.exports = () => ({
export default () => ({
fieldName: GT_FIELD_NAME,
strapiOperator: '$gt',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(GT_FIELD_NAME, { type });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const GTE_FIELD_NAME = 'gte';
module.exports = () => ({
export default () => ({
fieldName: GTE_FIELD_NAME,
strapiOperator: '$gte',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(GTE_FIELD_NAME, { type });
},
});

View File

@ -0,0 +1,14 @@
import { list } from 'nexus';
import type * as Nexus from 'nexus';
const IN_FIELD_NAME = 'in';
export default () => ({
fieldName: IN_FIELD_NAME,
strapiOperator: '$in',
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(IN_FIELD_NAME, { type: list(type) });
},
});

View File

@ -0,0 +1,54 @@
import type { Strapi } from '@strapi/types';
import { mapValues } from 'lodash/fp';
import andOperator from './and';
import orOperator from './or';
import notOperator from './not';
import eqOperator from './eq';
import eqiOperator from './eqi';
import neOperator from './ne';
import neiOperator from './nei';
import startsWithOperator from './starts-with';
import endsWithOperator from './ends-with';
import containsOperator from './contains';
import notContainsOperator from './not-contains';
import containsiOperator from './containsi';
import notContainsiOperator from './not-containsi';
import gtOperator from './gt';
import gteOperator from './gte';
import ltOperator from './lt';
import lteOperator from './lte';
import nullOperator from './null';
import notNullOperator from './not-null';
import inOperator from './in';
import notInOperator from './not-in';
import betweenOperator from './between';
const operators = {
and: andOperator,
or: orOperator,
not: notOperator,
eq: eqOperator,
eqi: eqiOperator,
ne: neOperator,
nei: neiOperator,
startsWith: startsWithOperator,
endsWith: endsWithOperator,
contains: containsOperator,
notContains: notContainsOperator,
containsi: containsiOperator,
notContainsi: notContainsiOperator,
gt: gtOperator,
gte: gteOperator,
lt: ltOperator,
lte: lteOperator,
null: nullOperator,
notNull: notNullOperator,
in: inOperator,
notIn: notInOperator,
between: betweenOperator,
};
// Instantiate every operator with the Strapi instance
export default ({ strapi }: { strapi: Strapi }) =>
mapValues((opCtor) => opCtor({ strapi }), operators);

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const LT_FIELD_NAME = 'lt';
module.exports = () => ({
export default () => ({
fieldName: LT_FIELD_NAME,
strapiOperator: '$lt',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(LT_FIELD_NAME, { type });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const LTE_FIELD_NAME = 'lte';
module.exports = () => ({
export default () => ({
fieldName: LTE_FIELD_NAME,
strapiOperator: '$lte',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(LTE_FIELD_NAME, { type });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const NE_FIELD_NAME = 'ne';
module.exports = () => ({
export default () => ({
fieldName: NE_FIELD_NAME,
strapiOperator: '$ne',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(NE_FIELD_NAME, { type });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const NEI_FIELD_NAME = 'nei';
module.exports = () => ({
export default () => ({
fieldName: NEI_FIELD_NAME,
strapiOperator: '$nei',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(NEI_FIELD_NAME, { type });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const NOT_CONTAINS_FIELD_NAME = 'notContains';
module.exports = () => ({
export default () => ({
fieldName: NOT_CONTAINS_FIELD_NAME,
strapiOperator: '$notContains',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(NOT_CONTAINS_FIELD_NAME, { type });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const NOT_CONTAINSI_FIELD_NAME = 'notContainsi';
module.exports = () => ({
export default () => ({
fieldName: NOT_CONTAINSI_FIELD_NAME,
strapiOperator: '$notContainsi',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(NOT_CONTAINSI_FIELD_NAME, { type });
},
});

View File

@ -1,15 +1,14 @@
'use strict';
const { list } = require('nexus');
import { list } from 'nexus';
import type * as Nexus from 'nexus';
const NOT_IN_FIELD_NAME = 'notIn';
module.exports = () => ({
export default () => ({
fieldName: NOT_IN_FIELD_NAME,
strapiOperator: '$notIn',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(NOT_IN_FIELD_NAME, { type: list(type) });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const NOT_NULL_FIELD_NAME = 'notNull';
module.exports = () => ({
export default () => ({
fieldName: NOT_NULL_FIELD_NAME,
strapiOperator: '$notNull',
add(t) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>) {
t.boolean(NOT_NULL_FIELD_NAME);
},
});

View File

@ -1,13 +1,14 @@
'use strict';
import type * as Nexus from 'nexus';
import type { Strapi } from '@strapi/types';
const NOT_FIELD_NAME = 'not';
module.exports = ({ strapi }) => ({
export default ({ strapi }: { strapi: Strapi }) => ({
fieldName: NOT_FIELD_NAME,
strapiOperator: '$not',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
const { naming, attributes } = strapi.plugin('graphql').service('utils');
if (attributes.isGraphQLScalar({ type })) {

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const NULL_FIELD_NAME = 'null';
module.exports = () => ({
export default () => ({
fieldName: NULL_FIELD_NAME,
strapiOperator: '$null',
add(t) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>) {
t.boolean(NULL_FIELD_NAME);
},
});

View File

@ -0,0 +1,14 @@
import { list } from 'nexus';
import type * as Nexus from 'nexus';
const OR_FIELD_NAME = 'or';
export default () => ({
fieldName: OR_FIELD_NAME,
strapiOperator: '$or',
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(OR_FIELD_NAME, { type: list(type) });
},
});

View File

@ -1,13 +1,13 @@
'use strict';
import type * as Nexus from 'nexus';
const STARTS_WITH_FIELD_NAME = 'startsWith';
module.exports = () => ({
export default () => ({
fieldName: STARTS_WITH_FIELD_NAME,
strapiOperator: '$startsWith',
add(t, type) {
add(t: Nexus.blocks.ObjectDefinitionBlock<string>, type: string) {
t.field(STARTS_WITH_FIELD_NAME, { type });
},
});

View File

@ -1,9 +1,9 @@
'use strict';
import { unionType } from 'nexus';
import { prop } from 'lodash/fp';
const { unionType } = require('nexus');
const { prop } = require('lodash/fp');
import type { Context } from '../types';
module.exports = ({ strapi, registry }) => {
export default ({ strapi, registry }: Context) => {
const { naming } = strapi.plugin('graphql').service('utils');
const { KINDS, GENERIC_MORPH_TYPENAME } = strapi.plugin('graphql').service('constants');
@ -12,7 +12,7 @@ module.exports = ({ strapi, registry }) => {
return unionType({
name: GENERIC_MORPH_TYPENAME,
resolveType(obj) {
resolveType(obj: any) {
const contentType = strapi.getModel(obj.__type);
if (!contentType) {
@ -26,7 +26,7 @@ module.exports = ({ strapi, registry }) => {
return naming.getTypeName(contentType);
},
definition(t) {
definition(t: any) {
const members = registry
// Resolve every content-type or component
.where(({ config }) => [KINDS.type, KINDS.component].includes(config.kind))

View File

@ -1,35 +1,35 @@
'use strict';
const { merge, map, pipe, reduce } = require('lodash/fp');
import { merge, map, pipe, reduce } from 'lodash/fp';
import type { Strapi } from '@strapi/types';
// Builders Factories
const enums = require('./enums');
const dynamicZone = require('./dynamic-zones');
const entity = require('./entity');
const entityMeta = require('./entity-meta');
const type = require('./type');
const response = require('./response');
const responseCollection = require('./response-collection');
const relationResponseCollection = require('./relation-response-collection');
const queries = require('./queries');
const mutations = require('./mutations');
const filters = require('./filters');
const inputs = require('./input');
const genericMorph = require('./generic-morph');
const resolvers = require('./resolvers');
import enums from './enums';
import dynamicZone from './dynamic-zones';
import entity from './entity';
import entityMeta from './entity-meta';
import typeBuilder from './type';
import response from './response';
import responseCollection from './response-collection';
import relationResponseCollection from './relation-response-collection';
import queries from './queries';
import mutations from './mutations';
import filters from './filters';
import inputs from './input';
import genericMorph from './generic-morph';
import resolvers from './resolvers';
// Misc
const operators = require('./filters/operators');
const utils = require('./utils');
import operators from './filters/operators';
import utils from './utils';
import type { TypeRegistry } from '../type-registry';
const buildersFactories = [
enums,
dynamicZone,
entity,
entityMeta,
type,
typeBuilder,
response,
responseCollection,
relationResponseCollection,
@ -41,21 +41,19 @@ const buildersFactories = [
resolvers,
];
module.exports = ({ strapi }) => {
export default ({ strapi }: { strapi: Strapi }) => {
const buildersMap = new Map();
return {
/**
* Instantiate every builder with a strapi instance & a type registry
* @param {string} name
* @param {object} registry
*/
new(name, registry) {
new(name: string, registry: TypeRegistry) {
const context = { strapi, registry };
const builders = pipe(
// Create a new instance of every builders
map((factory) => factory(context)),
map((factory: any) => factory(context)),
// Merge every builder into the same object
reduce(merge, {})
).call(null, buildersFactories);
@ -68,18 +66,16 @@ module.exports = ({ strapi }) => {
/**
* Delete a set of builders instances from
* the builders map for a given name
* @param {string} name
*/
delete(name) {
delete(name: string) {
buildersMap.delete(name);
},
/**
* Retrieve a set of builders instances from
* the builders map for a given name
* @param {string} name
*/
get(name) {
get(name: string) {
return buildersMap.get(name);
},

View File

@ -1,13 +1,11 @@
'use strict';
import { inputObjectType, nonNull } from 'nexus';
import { contentTypes } from '@strapi/utils';
import type { Schema } from '@strapi/types';
import type { Context } from '../types';
const { inputObjectType, nonNull } = require('nexus');
const {
contentTypes: { isWritableAttribute },
} = require('@strapi/utils');
module.exports = (context) => {
const { strapi } = context;
const { isWritableAttribute } = contentTypes;
export default ({ strapi }: Context) => {
const { naming, mappers, attributes } = strapi.plugin('graphql').service('utils');
const extension = strapi.plugin('graphql').service('extension');
@ -25,7 +23,7 @@ module.exports = (context) => {
} = attributes;
return {
buildInputType(contentType) {
buildInputType(contentType: Schema.Any) {
const { attributes, modelType } = contentType;
const name = (
@ -36,7 +34,7 @@ module.exports = (context) => {
name,
definition(t) {
const isFieldEnabled = (fieldName) => {
const isFieldEnabled = (fieldName: string) => {
return extension.shadowCRUD(contentType.uid).field(fieldName).hasInputEnabled();
};
@ -49,7 +47,7 @@ module.exports = (context) => {
t.id('id');
}
validAttributes.forEach(([attributeName, attribute]) => {
validAttributes.forEach(([attributeName, attribute]: [string, any]) => {
// Enums
if (isEnumeration(attribute)) {
const enumTypeName = getEnumName(contentType, attributeName);

View File

@ -1,9 +1,10 @@
'use strict';
import { extendType, nonNull } from 'nexus';
import { sanitize } from '@strapi/utils';
import type * as Nexus from 'nexus';
import type { Schema } from '@strapi/types';
import type { Context } from '../../types';
const { extendType, nonNull } = require('nexus');
const { sanitize } = require('@strapi/utils');
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const { service: getService } = strapi.plugin('graphql');
const { naming } = getService('utils');
@ -18,7 +19,10 @@ module.exports = ({ strapi }) => {
getContentTypeInputName,
} = naming;
const addCreateMutation = (t, contentType) => {
const addCreateMutation = (
t: Nexus.blocks.ObjectDefinitionBlock<'Mutation'>,
contentType: Schema.CollectionType
) => {
const { uid } = contentType;
const createMutationName = getCreateMutationTypeName(contentType);
@ -56,7 +60,10 @@ module.exports = ({ strapi }) => {
});
};
const addUpdateMutation = (t, contentType) => {
const addUpdateMutation = (
t: Nexus.blocks.ObjectDefinitionBlock<'Mutation'>,
contentType: Schema.CollectionType
) => {
const { uid } = contentType;
const updateMutationName = getUpdateMutationTypeName(contentType);
@ -103,7 +110,10 @@ module.exports = ({ strapi }) => {
});
};
const addDeleteMutation = (t, contentType) => {
const addDeleteMutation = (
t: Nexus.blocks.ObjectDefinitionBlock<'Mutation'>,
contentType: Schema.CollectionType
) => {
const { uid } = contentType;
const deleteMutationName = getDeleteMutationTypeName(contentType);
@ -138,18 +148,18 @@ module.exports = ({ strapi }) => {
};
return {
buildCollectionTypeMutations(contentType) {
buildCollectionTypeMutations(contentType: Schema.CollectionType) {
const createMutationName = `Mutation.${getCreateMutationTypeName(contentType)}`;
const updateMutationName = `Mutation.${getUpdateMutationTypeName(contentType)}`;
const deleteMutationName = `Mutation.${getDeleteMutationTypeName(contentType)}`;
const extension = getService('extension');
const registerAuthConfig = (action, auth) => {
const registerAuthConfig = (action: string, auth: any) => {
return extension.use({ resolversConfig: { [action]: { auth } } });
};
const isActionEnabled = (action) => {
const isActionEnabled = (action: string) => {
return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
};

View File

@ -0,0 +1,8 @@
import createCollectionTypeMutationsBuilder from './collection-type';
import createSingleTypeMutationsBuilder from './single-type';
import type { Context } from '../../types';
export default (context: Context) => ({
...createCollectionTypeMutationsBuilder(context),
...createSingleTypeMutationsBuilder(context),
});

View File

@ -1,14 +1,13 @@
'use strict';
import { extendType, nonNull } from 'nexus';
import { omit, isNil } from 'lodash/fp';
import { sanitize, validate, errors } from '@strapi/utils';
import type * as Nexus from 'nexus';
import type { Schema } from '@strapi/types';
import type { Context } from '../../types';
const { extendType, nonNull } = require('nexus');
const { omit, isNil } = require('lodash/fp');
const { NotFoundError } = errors;
const utils = require('@strapi/utils');
const { sanitize, validate } = utils;
const { NotFoundError } = utils.errors;
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const { service: getService } = strapi.plugin('graphql');
const { naming } = getService('utils');
@ -22,7 +21,10 @@ module.exports = ({ strapi }) => {
getDeleteMutationTypeName,
} = naming;
const addUpdateMutation = (t, contentType) => {
const addUpdateMutation = (
t: Nexus.blocks.ObjectDefinitionBlock<'Mutation'>,
contentType: Schema.SingleType
) => {
const { uid } = contentType;
const updateMutationName = getUpdateMutationTypeName(contentType);
@ -68,7 +70,7 @@ module.exports = ({ strapi }) => {
}
);
const entity = await strapi.entityService.findMany(uid, sanitizedQuery);
const entity = (await strapi.entityService!.findMany(uid, sanitizedQuery)) as any;
// Create or update
const value = isNil(entity)
@ -80,7 +82,10 @@ module.exports = ({ strapi }) => {
});
};
const addDeleteMutation = (t, contentType) => {
const addDeleteMutation = (
t: Nexus.blocks.ObjectDefinitionBlock<'Mutation'>,
contentType: Schema.SingleType
) => {
const { uid } = contentType;
const deleteMutationName = getDeleteMutationTypeName(contentType);
@ -107,7 +112,7 @@ module.exports = ({ strapi }) => {
auth: ctx?.state?.auth,
});
const entity = await strapi.entityService.findMany(uid, sanitizedQuery);
const entity = (await strapi.entityService!.findMany(uid, sanitizedQuery)) as any;
if (!entity) {
throw new NotFoundError('Entity not found');
@ -121,17 +126,17 @@ module.exports = ({ strapi }) => {
};
return {
buildSingleTypeMutations(contentType) {
buildSingleTypeMutations(contentType: Schema.SingleType) {
const updateMutationName = `Mutation.${getUpdateMutationTypeName(contentType)}`;
const deleteMutationName = `Mutation.${getDeleteMutationTypeName(contentType)}`;
const extension = getService('extension');
const registerAuthConfig = (action, auth) => {
const registerAuthConfig = (action: string, auth: any) => {
return extension.use({ resolversConfig: { [action]: { auth } } });
};
const isActionEnabled = (action) => {
const isActionEnabled = (action: string) => {
return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
};

View File

@ -1,8 +1,9 @@
'use strict';
import { extendType } from 'nexus';
import type * as Nexus from 'nexus';
import type { Schema } from '@strapi/types';
import type { Context } from '../../types';
const { extendType } = require('nexus');
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const { service: getService } = strapi.plugin('graphql');
const { naming } = getService('utils');
@ -16,17 +17,17 @@ module.exports = ({ strapi }) => {
getEntityResponseCollectionName,
} = naming;
const buildCollectionTypeQueries = (contentType) => {
const buildCollectionTypeQueries = (contentType: Schema.CollectionType) => {
const findOneQueryName = `Query.${getFindOneQueryName(contentType)}`;
const findQueryName = `Query.${getFindQueryName(contentType)}`;
const extension = getService('extension');
const registerAuthConfig = (action, auth) => {
const registerAuthConfig = (action: string, auth: any) => {
return extension.use({ resolversConfig: { [action]: { auth } } });
};
const isActionEnabled = (action) => {
const isActionEnabled = (action: string) => {
return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
};
@ -58,10 +59,11 @@ module.exports = ({ strapi }) => {
/**
* Register a "find one" query field to the nexus type definition
* @param {OutputDefinitionBlock<Query>} t
* @param contentType
*/
const addFindOneQuery = (t, contentType) => {
const addFindOneQuery = (
t: Nexus.blocks.ObjectDefinitionBlock<'Query'>,
contentType: Schema.CollectionType
) => {
const { uid } = contentType;
const findOneQueryName = getFindOneQueryName(contentType);
@ -89,10 +91,11 @@ module.exports = ({ strapi }) => {
/**
* Register a "find" query field to the nexus type definition
* @param {OutputDefinitionBlock<Query>} t
* @param contentType
*/
const addFindQuery = (t, contentType) => {
const addFindQuery = (
t: Nexus.blocks.ObjectDefinitionBlock<'Query'>,
contentType: Schema.CollectionType
) => {
const { uid } = contentType;
const findQueryName = getFindQueryName(contentType);

View File

@ -0,0 +1,8 @@
import createCollectionTypeQueriesBuilder from './collection-type';
import createSingleTypeQueriesBuilder from './single-type';
import type { Context } from '../../types';
export default (context: Context) => ({
...createCollectionTypeQueriesBuilder(context),
...createSingleTypeQueriesBuilder(context),
});

View File

@ -1,8 +1,9 @@
'use strict';
import { extendType } from 'nexus';
import type * as Nexus from 'nexus';
import type { Schema } from '@strapi/types';
import type { Context } from '../../types';
const { extendType } = require('nexus');
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const { service: getService } = strapi.plugin('graphql');
const { naming } = getService('utils');
@ -11,16 +12,16 @@ module.exports = ({ strapi }) => {
const { getFindOneQueryName, getEntityResponseName } = naming;
const buildSingleTypeQueries = (contentType) => {
const buildSingleTypeQueries = (contentType: Schema.SingleType) => {
const findQueryName = `Query.${getFindOneQueryName(contentType)}`;
const extension = getService('extension');
const registerAuthConfig = (action, auth) => {
const registerAuthConfig = (action: string, auth: any) => {
return extension.use({ resolversConfig: { [action]: { auth } } });
};
const isActionEnabled = (action) => {
const isActionEnabled = (action: string) => {
return extension.shadowCRUD(contentType.uid).isActionEnabled(action);
};
@ -41,7 +42,10 @@ module.exports = ({ strapi }) => {
});
};
const addFindQuery = (t, contentType) => {
const addFindQuery = (
t: Nexus.blocks.ObjectDefinitionBlock<string>,
contentType: Schema.SingleType
) => {
const { uid } = contentType;
const findQueryName = getFindOneQueryName(contentType);

View File

@ -1,18 +1,16 @@
'use strict';
import { objectType, nonNull } from 'nexus';
import { defaultTo, prop, pipe } from 'lodash/fp';
import type { Schema } from '@strapi/types';
import type { Context } from '../types';
const { objectType, nonNull } = require('nexus');
const { defaultTo, prop, pipe } = require('lodash/fp');
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const { naming } = strapi.plugin('graphql').service('utils');
return {
/**
* Build a type definition for a content API relation's collection response for a given content type
* @param {object} contentType The content type which will be used to build its content API response definition
* @return {NexusObjectTypeDef}
*/
buildRelationResponseCollectionDefinition(contentType) {
buildRelationResponseCollectionDefinition(contentType: Schema.ContentType) {
const name = naming.getRelationResponseCollectionName(contentType);
const entityName = naming.getEntityName(contentType);

View File

@ -1,13 +1,12 @@
'use strict';
import { get } from 'lodash/fp';
import { sanitize, validate, pipeAsync, errors } from '@strapi/utils';
import type { UID } from '@strapi/types';
const { get } = require('lodash/fp');
import type { Context } from '../../types';
const utils = require('@strapi/utils');
const { ApplicationError } = errors;
const { sanitize, validate, pipeAsync } = utils;
const { ApplicationError } = utils.errors;
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const { service: getGraphQLService } = strapi.plugin('graphql');
const { isMorphRelation, isMedia } = getGraphQLService('utils').attributes;
@ -15,9 +14,15 @@ module.exports = ({ strapi }) => {
const { toEntityResponse, toEntityResponseCollection } = getGraphQLService('format').returnTypes;
return {
buildAssociationResolver({ contentTypeUID, attributeName }) {
buildAssociationResolver({
contentTypeUID,
attributeName,
}: {
contentTypeUID: UID.ContentType;
attributeName: string;
}) {
const contentType = strapi.getModel(contentTypeUID);
const attribute = contentType.attributes[attributeName];
const attribute: any = contentType.attributes[attributeName];
if (!attribute) {
throw new ApplicationError(
@ -33,7 +38,7 @@ module.exports = ({ strapi }) => {
const targetContentType = strapi.getModel(targetUID);
return async (parent, args = {}, context = {}) => {
return async (parent: any, args: any = {}, context: any = {}) => {
const { auth } = context.state;
const transformedArgs = transformArgs(args, {
@ -48,7 +53,7 @@ module.exports = ({ strapi }) => {
auth,
});
const data = await strapi.entityService.load(
const data = await strapi.entityService!.load(
contentTypeUID,
parent,
attributeName,
@ -65,8 +70,8 @@ module.exports = ({ strapi }) => {
// so that the sanitize util can work properly.
if (isMorphAttribute) {
// Helpers used for the data cleanup
const wrapData = (dataToWrap) => ({ [attributeName]: dataToWrap });
const sanitizeData = (dataToSanitize) => {
const wrapData = (dataToWrap: any) => ({ [attributeName]: dataToWrap });
const sanitizeData = (dataToSanitize: any) => {
return sanitize.contentAPI.output(dataToSanitize, contentType, { auth });
};
const unwrapData = get(attributeName);

View File

@ -1,15 +1,24 @@
'use strict';
import { sanitize, validate } from '@strapi/utils';
import type { Attribute, UID } from '@strapi/types';
const { sanitize, validate } = require('@strapi/utils');
import type { Context } from '../../types';
module.exports = ({ strapi }) => ({
buildComponentResolver({ contentTypeUID, attributeName }) {
export default ({ strapi }: Context) => ({
buildComponentResolver({
contentTypeUID,
attributeName,
}: {
contentTypeUID: UID.ContentType;
attributeName: string;
}) {
const { transformArgs } = strapi.plugin('graphql').service('builders').utils;
return async (parent, args = {}, ctx) => {
return async (parent: any, args: any, ctx: any) => {
const contentType = strapi.getModel(contentTypeUID);
const { component: componentName } = contentType.attributes[attributeName];
const { component: componentName } = contentType.attributes[
attributeName
] as Attribute.Component;
const component = strapi.getModel(componentName);
const transformedArgs = transformArgs(args, { contentType: component, usePagination: true });
@ -19,7 +28,7 @@ module.exports = ({ strapi }) => ({
const sanitizedQuery = await sanitize.contentAPI.query(transformedArgs, component, {
auth: ctx?.state?.auth,
});
return strapi.entityService.load(contentTypeUID, parent, attributeName, sanitizedQuery);
return strapi.entityService!.load(contentTypeUID, parent, attributeName, sanitizedQuery);
};
},
});

View File

@ -0,0 +1,17 @@
import type { UID } from '@strapi/types';
import type { Context } from '../../types';
export default ({ strapi }: Context) => ({
buildDynamicZoneResolver({
contentTypeUID,
attributeName,
}: {
contentTypeUID: UID.ContentType;
attributeName: string;
}) {
return async (parent: any) => {
return strapi.entityService!.load(contentTypeUID, parent, attributeName);
};
},
});

View File

@ -0,0 +1,18 @@
import associationResolvers from './association';
import queriesResolvers from './query';
import mutationsResolvers from './mutation';
import componentResolvers from './component';
import dynamicZoneResolvers from './dynamic-zone';
import type { Context } from '../../types';
export default (context: Context) => ({
// Generics
...associationResolvers(context),
// Builders
...mutationsResolvers(context),
...queriesResolvers(context),
...componentResolvers(context),
...dynamicZoneResolvers(context),
});

View File

@ -1,29 +1,29 @@
'use strict';
const { pick } = require('lodash/fp');
const { sanitize, validate } = require('@strapi/utils');
import { pick } from 'lodash/fp';
import { sanitize, validate } from '@strapi/utils';
import type { Schema } from '@strapi/types';
import type { Context } from '../../types';
const pickCreateArgs = pick(['params', 'data', 'files']);
module.exports = ({ strapi }) => ({
buildMutationsResolvers({ contentType }) {
export default ({ strapi }: Context) => ({
buildMutationsResolvers({ contentType }: { contentType: Schema.ContentType }) {
const { uid } = contentType;
return {
async create(parent, args) {
async create(parent: any, args: any) {
// todo[v4]: Might be interesting to generate dynamic yup schema to validate payloads with more complex checks (on top of graphql validation)
const params = pickCreateArgs(args);
// todo[v4]: Sanitize args to only keep params / data / files (or do it in the base resolver)
return strapi.entityService.create(uid, params);
return strapi.entityService!.create(uid, params);
},
async update(parent, args) {
async update(parent: any, args: any) {
const { id, data } = args;
return strapi.entityService.update(uid, id, { data });
return strapi.entityService!.update(uid, id, { data });
},
async delete(parent, args, ctx) {
async delete(parent: any, args: any, ctx: any) {
const { id, ...rest } = args;
await validate.contentAPI.query(rest, contentType, {
auth: ctx?.state?.auth,
@ -31,7 +31,7 @@ module.exports = ({ strapi }) => ({
const sanitizedQuery = await sanitize.contentAPI.query(rest, contentType, {
auth: ctx?.state?.auth,
});
return strapi.entityService.delete(uid, id, sanitizedQuery);
return strapi.entityService!.delete(uid, id, sanitizedQuery);
},
};
},

View File

@ -0,0 +1,34 @@
import { omit } from 'lodash/fp';
import { sanitize, validate } from '@strapi/utils';
import type { Schema } from '@strapi/types';
import type { Context } from '../../types';
export default ({ strapi }: Context) => ({
buildQueriesResolvers({ contentType }: { contentType: Schema.ContentType }) {
const { uid } = contentType;
return {
async find(parent: any, args: any, ctx: any) {
await validate.contentAPI.query(args, contentType, {
auth: ctx?.state?.auth,
});
const sanitizedQuery = await sanitize.contentAPI.query(args, contentType, {
auth: ctx?.state?.auth,
});
return strapi.entityService!.findMany(uid, sanitizedQuery);
},
async findOne(parent: any, args: any, ctx: any) {
await validate.contentAPI.query(args, contentType, {
auth: ctx?.state?.auth,
});
const sanitizedQuery = await sanitize.contentAPI.query(args, contentType, {
auth: ctx?.state?.auth,
});
return strapi.entityService!.findOne(uid, args.id, omit('id', sanitizedQuery));
},
};
},
});

View File

@ -1,19 +1,19 @@
'use strict';
import { objectType, nonNull } from 'nexus';
import { defaultTo, prop, pipe } from 'lodash/fp';
import type { Schema } from '@strapi/types';
import type { Context } from '../types';
const { objectType, nonNull } = require('nexus');
const { defaultTo, prop, pipe } = require('lodash/fp');
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const { naming } = strapi.plugin('graphql').service('utils');
const { RESPONSE_COLLECTION_META_TYPE_NAME } = strapi.plugin('graphql').service('constants');
return {
/**
* Build a type definition for a content API collection response for a given content type
* @param {object} contentType The content type which will be used to build its content API response definition
* @param {Schema.ContentType} contentType The content type which will be used to build its content API response definition
* @return {NexusObjectTypeDef}
*/
buildResponseCollectionDefinition(contentType) {
buildResponseCollectionDefinition(contentType: Schema.ContentType) {
const name = naming.getEntityResponseCollectionName(contentType);
const entityName = naming.getEntityName(contentType);

View File

@ -1,18 +1,16 @@
'use strict';
import { objectType } from 'nexus';
import { prop } from 'lodash/fp';
import type { Schema } from '@strapi/types';
import type { Context } from '../types';
const { objectType } = require('nexus');
const { prop } = require('lodash/fp');
module.exports = ({ strapi }) => {
export default ({ strapi }: Context) => {
const { naming } = strapi.plugin('graphql').service('utils');
return {
/**
* Build a type definition for a content API response for a given content type
* @param {object} contentType The content type which will be used to build its content API response definition
* @return {NexusObjectTypeDef}
*/
buildResponseDefinition(contentType) {
buildResponseDefinition(contentType: Schema.ContentType) {
const name = naming.getEntityResponseName(contentType);
const entityName = naming.getEntityName(contentType);

View File

@ -1,24 +1,20 @@
'use strict';
import { isArray, isString, isUndefined, constant } from 'lodash/fp';
import { objectType } from 'nexus';
import { contentTypes } from '@strapi/utils';
import type { Schema } from '@strapi/types';
const { isArray, isString, isUndefined, constant } = require('lodash/fp');
const { objectType } = require('nexus');
import type { Context } from '../types';
const { contentTypes } = require('@strapi/utils');
export type TypeBuildersOptions = {
builder: any;
attributeName: string;
attribute: any;
contentType: any;
context: Context;
};
/**
* @typedef TypeBuildersOptions
*
* @property {ObjectDefinitionBlock} builder
* @property {string} attributeName
* @property {object} attribute
* @property {object} contentType
* @property {object} context
* @property {object} context.strapi
* @property {object} context.registry
*/
module.exports = (context) => {
export default (context: Context) => {
const { strapi } = context;
const getGraphQLService = strapi.plugin('graphql').service;
const extension = getGraphQLService('extension');
@ -28,10 +24,10 @@ module.exports = (context) => {
*
* The attribute is added based on a simple association between a Strapi
* type and a GraphQL type (the map is defined in `strapiTypeToGraphQLScalar`)
*
* @param {TypeBuildersOptions} options
*/
const addScalarAttribute = ({ builder, attributeName, attribute }) => {
const addScalarAttribute = (options: TypeBuildersOptions) => {
const { builder, attributeName, attribute } = options;
const { mappers } = getGraphQLService('utils');
const gqlType = mappers.strapiScalarToGraphQLScalar(attribute.type);
@ -44,10 +40,10 @@ module.exports = (context) => {
*
* The attribute is added by fetching the component's type
* name and using it as the attribute's type
*
* @param {TypeBuildersOptions} options
*/
const addComponentAttribute = ({ builder, attributeName, contentType, attribute }) => {
const addComponentAttribute = (options: TypeBuildersOptions) => {
const { builder, attributeName, contentType, attribute } = options;
let localBuilder = builder;
const { naming } = getGraphQLService('utils');
@ -78,10 +74,10 @@ module.exports = (context) => {
*
* The attribute is added by fetching the dynamic zone's
* type name and using it as the attribute's type
*
* @param {TypeBuildersOptions} options
*/
const addDynamicZoneAttribute = ({ builder, attributeName, contentType }) => {
const addDynamicZoneAttribute = (options: TypeBuildersOptions) => {
const { builder, attributeName, contentType } = options;
const { naming } = getGraphQLService('utils');
const { ERROR_CODES } = getGraphQLService('constants');
const { buildDynamicZoneResolver } = getGraphQLService('builders').get('content-api');
@ -111,10 +107,10 @@ module.exports = (context) => {
*
* The attribute is added by fetching the enum's type
* name and using it as the attribute's type
*
* @param {TypeBuildersOptions} options
*/
const addEnumAttribute = ({ builder, attributeName, contentType }) => {
const addEnumAttribute = (options: TypeBuildersOptions) => {
const { builder, attributeName, contentType } = options;
const { naming } = getGraphQLService('utils');
const type = naming.getEnumName(contentType, attributeName);
@ -124,9 +120,8 @@ module.exports = (context) => {
/**
* Add a media attribute to the type definition
* @param {TypeBuildersOptions} options
*/
const addMediaAttribute = (options) => {
const addMediaAttribute = (options: TypeBuildersOptions) => {
const { naming } = getGraphQLService('utils');
const { getContentTypeArgs } = getGraphQLService('builders').utils;
const { buildAssociationResolver } = getGraphQLService('builders').get('content-api');
@ -158,9 +153,8 @@ module.exports = (context) => {
/**
* Add a polymorphic relational attribute to the type definition
* @param {TypeBuildersOptions} options
*/
const addPolymorphicRelationalAttribute = (options) => {
const addPolymorphicRelationalAttribute = (options: TypeBuildersOptions) => {
const { GENERIC_MORPH_TYPENAME } = getGraphQLService('constants');
const { naming } = getGraphQLService('utils');
const { buildAssociationResolver } = getGraphQLService('builders').get('content-api');
@ -201,9 +195,8 @@ module.exports = (context) => {
/**
* Add a regular relational attribute to the type definition
* @param {TypeBuildersOptions} options
*/
const addRegularRelationalAttribute = (options) => {
const addRegularRelationalAttribute = (options: TypeBuildersOptions) => {
const { naming } = getGraphQLService('utils');
const { getContentTypeArgs } = getGraphQLService('builders').utils;
const { buildAssociationResolver } = getGraphQLService('builders').get('content-api');
@ -240,11 +233,11 @@ module.exports = (context) => {
builder.field(attributeName, { type, resolve, args });
};
const isNotPrivate = (contentType) => (attributeName) => {
const isNotPrivate = (contentType: Schema.Any) => (attributeName: string) => {
return !contentTypes.isPrivateAttribute(contentType, attributeName);
};
const isNotDisabled = (contentType) => (attributeName) => {
const isNotDisabled = (contentType: Schema.Any) => (attributeName: string) => {
return extension.shadowCRUD(contentType.uid).field(attributeName).hasOutputEnabled();
};
@ -254,7 +247,7 @@ module.exports = (context) => {
* @param contentType - The content type used to created the definition
* @return {NexusObjectTypeDef}
*/
buildTypeDefinition(contentType) {
buildTypeDefinition(contentType: Schema.Any) {
const utils = getGraphQLService('utils');
const { getComponentName, getTypeName } = utils.naming;
@ -310,16 +303,19 @@ module.exports = (context) => {
// We create a copy of the builder (t) to apply custom
// rules only on the current attribute (eg: nonNull, list, ...)
let builder = t;
let builder: any = t;
if (attribute.required) {
builder = builder.nonNull;
}
/**
* @type {TypeBuildersOptions}
*/
const options = { builder, attributeName, attribute, contentType, context };
const options: TypeBuildersOptions = {
builder,
attributeName,
attribute,
contentType,
context,
};
// Enums
if (isEnumeration(attribute)) {

View File

@ -1,12 +1,11 @@
'use strict';
import { entries, mapValues, omit } from 'lodash/fp';
import { pagination, contentTypes } from '@strapi/utils';
import type { Strapi, Schema } from '@strapi/types';
const { entries, mapValues, omit } = require('lodash/fp');
const {
pagination: { withDefaultPagination },
contentTypes: { hasDraftAndPublish },
} = require('@strapi/utils');
const { withDefaultPagination } = pagination;
const { hasDraftAndPublish } = contentTypes;
module.exports = ({ strapi }) => {
export default ({ strapi }: { strapi: Strapi }) => {
const { service: getService } = strapi.plugin('graphql');
return {
@ -17,11 +16,11 @@ module.exports = ({ strapi }) => {
* @param {boolean} options.multiple
* @return {object}
*/
getContentTypeArgs(contentType, { multiple = true } = {}) {
getContentTypeArgs(contentType: Schema.Any, { multiple = true } = {}) {
const { naming } = getService('utils');
const { args } = getService('internals');
const { kind, modelType } = contentType;
const { modelType } = contentType;
// Components
if (modelType === 'component') {
@ -34,6 +33,8 @@ module.exports = ({ strapi }) => {
};
}
const { kind } = contentType;
// Collection Types
if (kind === 'collectionType') {
if (!multiple) {
@ -67,14 +68,12 @@ module.exports = ({ strapi }) => {
/**
* Filter an object entries and keep only those whose value is a unique scalar attribute
* @param {object} attributes
* @return {Object<string, object>}
*/
getUniqueScalarAttributes(attributes) {
getUniqueScalarAttributes(attributes: Schema.Attributes) {
const { isStrapiScalar } = getService('utils').attributes;
const uniqueAttributes = entries(attributes).filter(
([, attribute]) => isStrapiScalar(attribute) && attribute.unique
([, attribute]) => isStrapiScalar(attribute) && 'unique' in attribute && attribute.unique
);
return Object.fromEntries(uniqueAttributes);
@ -85,18 +84,26 @@ module.exports = ({ strapi }) => {
* @param {object} attributes - The attributes object to transform
* @return {Object<string, string>}
*/
scalarAttributesToFiltersMap: mapValues((attribute) => {
const { mappers, naming } = getService('utils');
scalarAttributesToFiltersMap(attributes: Schema.Attributes) {
return mapValues((attribute) => {
const { mappers, naming } = getService('utils');
const gqlScalar = mappers.strapiScalarToGraphQLScalar(attribute.type);
const gqlScalar = mappers.strapiScalarToGraphQLScalar(attribute.type);
return naming.getScalarFilterInputTypeName(gqlScalar);
}),
return naming.getScalarFilterInputTypeName(gqlScalar);
}, attributes);
},
/**
* Apply basic transform to GQL args
*/
transformArgs(args, { contentType, usePagination = false } = {}) {
transformArgs(
args: any,
{
contentType,
usePagination = false,
}: { contentType: Schema.ContentType; usePagination?: boolean }
) {
const { mappers } = getService('utils');
const { config } = strapi.plugin('graphql');
const { pagination = {}, filters = {} } = args;
@ -106,8 +113,8 @@ module.exports = ({ strapi }) => {
// Pagination
if (usePagination) {
const defaultLimit = config('defaultLimit');
const maxLimit = config('maxLimit');
const defaultLimit: number = config('defaultLimit');
const maxLimit: number = config('maxLimit');
Object.assign(
newArgs,

View File

@ -1,5 +1,3 @@
'use strict';
const PAGINATION_TYPE_NAME = 'Pagination';
const PUBLICATION_STATE_TYPE_NAME = 'PublicationState';
const ERROR_TYPE_NAME = 'Error';
@ -17,7 +15,7 @@ const GRAPHQL_SCALARS = [
'Date',
'Time',
'DateTime',
];
] as const;
const STRAPI_SCALARS = [
'boolean',
@ -38,7 +36,7 @@ const STRAPI_SCALARS = [
'email',
'password',
'text',
];
] as const;
const SCALARS_ASSOCIATIONS = {
uid: 'String',
@ -59,7 +57,7 @@ const SCALARS_ASSOCIATIONS = {
time: 'Time',
datetime: 'DateTime',
timestamp: 'DateTime',
};
} as const;
const GENERIC_MORPH_TYPENAME = 'GenericMorph';
@ -79,7 +77,7 @@ const KINDS = {
scalar: 'scalar',
morph: 'polymorphic',
internal: 'internal',
};
} as const;
const allOperators = [
'and',
@ -113,7 +111,7 @@ const allOperators = [
'notIn',
'between',
];
] as const;
const GRAPHQL_SCALAR_OPERATORS = {
// ID
@ -132,13 +130,27 @@ const GRAPHQL_SCALAR_OPERATORS = {
DateTime: allOperators,
// Others
JSON: allOperators,
};
} as const;
const ERROR_CODES = {
emptyDynamicZone: 'dynamiczone.empty',
} as const;
export type Constants = {
PAGINATION_TYPE_NAME: string;
RESPONSE_COLLECTION_META_TYPE_NAME: string;
PUBLICATION_STATE_TYPE_NAME: string;
GRAPHQL_SCALARS: string[];
STRAPI_SCALARS: string[];
GENERIC_MORPH_TYPENAME: string;
KINDS: typeof KINDS;
GRAPHQL_SCALAR_OPERATORS: typeof GRAPHQL_SCALAR_OPERATORS;
SCALARS_ASSOCIATIONS: typeof SCALARS_ASSOCIATIONS;
ERROR_CODES: typeof ERROR_CODES;
ERROR_TYPE_NAME: string;
};
module.exports = () => ({
export default () => ({
PAGINATION_TYPE_NAME,
RESPONSE_COLLECTION_META_TYPE_NAME,
PUBLICATION_STATE_TYPE_NAME,

View File

@ -1,27 +1,32 @@
'use strict';
import { pruneSchema } from '@graphql-tools/utils';
import { makeSchema } from 'nexus';
import { prop, startsWith } from 'lodash/fp';
import type * as Nexus from 'nexus';
import type { Schema, Strapi } from '@strapi/types';
const { mergeSchemas, addResolversToSchema } = require('@graphql-tools/schema');
const { pruneSchema } = require('@graphql-tools/utils');
const { makeSchema } = require('nexus');
const { prop, startsWith } = require('lodash/fp');
const { wrapResolvers } = require('./wrap-resolvers');
const {
import { wrapResolvers } from './wrap-resolvers';
import {
registerSingleType,
registerCollectionType,
registerComponent,
registerScalars,
registerInternals,
registerPolymorphicContentType,
contentType: {
registerEnumsDefinition,
registerInputsDefinition,
registerFiltersDefinition,
registerDynamicZonesDefinition,
},
} = require('./register-functions');
contentType,
} from './register-functions';
import { TypeRegistry } from '../type-registry';
const {
registerEnumsDefinition,
registerInputsDefinition,
registerFiltersDefinition,
registerDynamicZonesDefinition,
} = contentType;
export default ({ strapi }: { strapi: Strapi }) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { mergeSchemas, addResolversToSchema } = require('@graphql-tools/schema');
module.exports = ({ strapi }) => {
const { service: getGraphQLService } = strapi.plugin('graphql');
const { config } = strapi.plugin('graphql');
@ -29,9 +34,9 @@ module.exports = ({ strapi }) => {
const extensionService = getGraphQLService('extension');
// Type Registry
let registry;
let registry: any;
// Builders Instances
let builders;
let builders: any;
const buildSchema = () => {
const isShadowCRUDEnabled = !!config('shadowCRUD');
@ -63,7 +68,7 @@ module.exports = ({ strapi }) => {
const schemaWithResolvers = addResolversToSchema(schema, extension.resolvers);
// Create a configuration object for the artifacts generation
const outputs = {
const outputs: Nexus.core.SchemaConfig['outputs'] = {
schema: config('artifacts.schema', false),
typegen: config('artifacts.typegen', false),
};
@ -71,6 +76,7 @@ module.exports = ({ strapi }) => {
const currentEnv = strapi.config.get('environment');
const nexusSchema = makeSchema({
types: [],
// Build the schema from the merged GraphQL schema.
// Since we're passing the schema to the mergeSchema property, it'll transform our SDL type definitions
// into Nexus type definition, thus allowing them to be handled by Nexus plugins & other processing
@ -97,7 +103,7 @@ module.exports = ({ strapi }) => {
return prunedNexusSchema;
};
const buildMergedSchema = ({ registry }) => {
const buildMergedSchema = ({ registry }: { registry: TypeRegistry }) => {
// Here we extract types, plugins & typeDefs from a temporary generated
// extension since there won't be any addition allowed after schemas generation
const { types, typeDefs = [] } = extensionService.generate({ typeRegistry: registry });
@ -144,9 +150,9 @@ module.exports = ({ strapi }) => {
* Register needed GraphQL types for every content type
* @param {object[]} contentTypes
*/
const registerAPITypes = (contentTypes) => {
const registerAPITypes = (contentTypes: Schema.Any[]) => {
for (const contentType of contentTypes) {
const { kind, modelType } = contentType;
const { modelType } = contentType;
const registerOptions = { registry, strapi, builders };
@ -160,10 +166,13 @@ module.exports = ({ strapi }) => {
// Generate & register component's definition
if (modelType === 'component') {
registerComponent(contentType, registerOptions);
continue;
}
const { kind } = contentType;
// Generate & register single type's definition
else if (kind === 'singleType') {
if (kind === 'singleType') {
registerSingleType(contentType, registerOptions);
}
@ -174,7 +183,7 @@ module.exports = ({ strapi }) => {
}
};
const registerMorphTypes = (contentTypes) => {
const registerMorphTypes = (contentTypes: Schema.Any[]) => {
// Create & register a union type that includes every type or component registered
const genericMorphType = builders.buildGenericMorphDefinition();
registry.register(GENERIC_MORPH_TYPENAME, genericMorphType, { kind: KINDS.morph });

View File

@ -0,0 +1,72 @@
import { propOr } from 'lodash/fp';
import { GraphQLFieldResolver, GraphQLResolveInfo } from 'graphql';
import { policy as policyUtils, errors } from '@strapi/utils';
import type { Strapi } from '@strapi/types';
const { PolicyError } = errors;
const getPoliciesConfig = propOr([], 'policies');
const createPoliciesMiddleware = (resolverConfig: any, { strapi }: { strapi: Strapi }) => {
const resolverPolicies = getPoliciesConfig(resolverConfig);
const policies = policyUtils.resolve(resolverPolicies, {});
return async (
resolve: GraphQLFieldResolver<any, any>,
parent: any,
args: any,
context: any,
info: GraphQLResolveInfo
) => {
// Create a graphql policy context
const policyContext = createGraphQLPolicyContext(parent, args, context, info);
// Run policies & throw an error if one of them fails
for (const { handler, config } of policies) {
const result = await handler(policyContext, config, { strapi });
if (![true, undefined].includes(result)) {
throw new PolicyError();
}
}
return resolve(parent, args, context, info);
};
};
const createGraphQLPolicyContext = (
parent: any,
args: any,
context: any,
info: GraphQLResolveInfo
) => {
const policyContext = {
get parent() {
return parent;
},
get args() {
return args;
},
get context() {
return context;
},
get info() {
return info;
},
get state() {
return this.context.state;
},
get http() {
return this.context.koaContext;
},
};
return policyUtils.createPolicyContext('graphql', policyContext);
};
export { createPoliciesMiddleware };

View File

@ -1,6 +1,18 @@
'use strict';
import type { Strapi, Schema } from '@strapi/types';
import type { TypeRegistry } from '../../type-registry';
const registerCollectionType = (contentType, { registry, strapi, builders }) => {
const registerCollectionType = (
contentType: Schema.CollectionType,
{
registry,
strapi,
builders,
}: {
registry: TypeRegistry;
strapi: Strapi;
builders: any;
}
) => {
const { service: getService } = strapi.plugin('graphql');
const { naming } = getService('utils');
@ -19,7 +31,7 @@ const registerCollectionType = (contentType, { registry, strapi, builders }) =>
mutations: naming.getEntityMutationsTypeName(contentType),
};
const getConfig = (kind) => ({ kind, contentType });
const getConfig = (kind: string) => ({ kind, contentType });
// Type definition
registry.register(types.base, builders.buildTypeDefinition(contentType), getConfig(KINDS.type));
@ -69,4 +81,4 @@ const registerCollectionType = (contentType, { registry, strapi, builders }) =>
}
};
module.exports = { registerCollectionType };
export { registerCollectionType };

View File

@ -1,6 +1,18 @@
'use strict';
import type { Strapi, Schema } from '@strapi/types';
import type { TypeRegistry } from '../../type-registry';
const registerComponent = (contentType, { registry, strapi, builders }) => {
const registerComponent = (
contentType: Schema.Component,
{
registry,
strapi,
builders,
}: {
registry: TypeRegistry;
strapi: Strapi;
builders: any;
}
) => {
const { service: getService } = strapi.plugin('graphql');
const { getComponentName } = getService('utils').naming;
@ -12,4 +24,4 @@ const registerComponent = (contentType, { registry, strapi, builders }) => {
registry.register(name, definition, { kind: KINDS.component, contentType });
};
module.exports = { registerComponent };
export { registerComponent };

Some files were not shown because too many files have changed in this diff Show More