@backland/schema
Version:
TypeScript schema declaration and validation library with static type inference
641 lines (634 loc) • 20 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.GraphQLParser = void 0;
exports.createHooks = createHooks;
exports.describeField = describeField;
var _utils = require("@backland/utils");
var _graphql = require("graphql");
var _ObjectType = require("../ObjectType");
var _AliasField = require("../fields/AliasField");
var _LiteralField = require("../fields/LiteralField");
var _MetaFieldField = require("../fields/MetaFieldField");
var _ObjectField = require("../fields/ObjectField");
var _UnionField = require("../fields/UnionField");
var _isHiddenFieldName = require("../isHiddenFieldName");
var _parseTypeName = require("../parseTypeName");
var _GraphQLDateType = require("./GraphQLDateType");
var _GraphQLNullType = require("./GraphQLNullType");
var _GraphQLPhoneType = require("./GraphQLPhoneType");
var _GraphQLUlidType = require("./GraphQLUlidType");
function createHooks() {
return {
onField: _utils.hooks.parallel(),
onFieldConfigMap: _utils.hooks.parallel(),
onFieldResult: _utils.hooks.parallel(),
willCreateObjectType: _utils.hooks.parallel()
};
}
const resultsCache = (0, _utils.createStore)();
const graphqlTypesRegister = (0, _utils.createStore)();
const fieldsRegister = (0, _utils.createStore)();
function wrapCreationWithCache(name, create) {
return function creator(...args) {
if (graphqlTypesRegister.has(name)) {
return graphqlTypesRegister.get(name);
}
const result = create(...args);
graphqlTypesRegister.set(name, result);
return graphqlTypesRegister.get(name);
};
}
class GraphQLParser {
static resultsCache = resultsCache;
static graphqlTypesRegister = graphqlTypesRegister;
static fieldsRegister = fieldsRegister;
static reset = () => {
resultsCache.clear();
graphqlTypesRegister.clear();
fieldsRegister.clear();
};
static objectIds = object => {
if (!(0, _ObjectType.isObject)(object)) {
throw new _utils.RuntimeError(`Invalid Object.`, {
object
});
}
const {
id: objectId
} = (0, _utils.nonNullValues)({
id: object.id
}, 'The provided object should be identified before converting. ' + 'You can use object.identify("abc")');
const cacheId = `${[objectId].filter(Boolean).join()}`;
//
return {
cacheId,
object,
objectId
};
};
static objectToGraphQL(init) {
const {
path
} = init;
const objectInput = init.object;
const {
cacheId,
objectId
} = this.objectIds(objectInput);
const {
implements: parents
} = objectInput.meta;
if (resultsCache.has(cacheId)) {
return resultsCache.get(cacheId);
}
// save reference for circular dependencies
const graphqlParsed = {};
resultsCache.set(cacheId, graphqlParsed);
const buildFields = (options, _getType) => {
const {
interfaces: currentInterfacesOption
} = options;
options.interfaces = () => {
const currentInterfaces = typeof currentInterfacesOption === 'function' ? currentInterfacesOption() : currentInterfacesOption || [];
const types = parents ? parents.map(parent => _ObjectType.ObjectType.register.get(parent)) : [];
return [...currentInterfaces, ...types.map(type => {
return type.graphqlInterfaceType();
})];
};
options.fields = () => {
var _options$middleware;
const helpers = objectInput.helpers();
const objectMiddleware = objectInput.graphQLMiddleware || [];
const builders = [];
const hooks = createHooks();
(_options$middleware = options.middleware) === null || _options$middleware === void 0 ? void 0 : _options$middleware.call(options, hooks);
objectMiddleware.forEach(fn => fn(hooks));
const fieldsConfigMap = {};
const aliases = [];
helpers.list.forEach(({
name: fieldName,
instance,
plainField
}) => {
if ((0, _isHiddenFieldName.isHiddenFieldName)(fieldName)) return;
if (plainField.hidden) return;
if (plainField.type === 'alias') {
_AliasField.AliasField.assert(instance);
const subTypeName = (0, _parseTypeName.parseTypeName)({
field: plainField,
fieldName,
parentName: objectId
});
aliases.push({
fieldName: subTypeName,
instance,
parentName: objectId,
path: [objectId, fieldName]
});
return;
}
const field = this.fieldToGraphQL({
field: instance,
fieldName,
parentName: objectId,
path: path || [objectId]
});
hooks.onFieldResult.exec(field);
builders.push(field);
});
function _useConvertFieldResult(next) {
const field = {
description: next.description,
type: _getType(next)
};
const origin =
// @ts-ignore
objectInput.definition[next.fieldName] || undefined;
if (origin !== null && origin !== void 0 && origin.hidden) return;
if ((origin === null || origin === void 0 ? void 0 : origin.defaultValue) !== undefined) {
// @ts-ignore
field.defaultValue = origin.defaultValue;
}
fieldsConfigMap[next.fieldName] = field;
hooks.onField.exec(next, field);
}
builders.forEach(_useConvertFieldResult);
aliases.forEach(({
instance,
fieldName,
parentName,
path
}) => {
const {
type
} = instance.asFinalFieldDef.def;
_useConvertFieldResult(this.fieldToGraphQL({
field: (0, _ObjectType.__getCachedFieldInstance)(type),
fieldName,
parentName,
path
}));
});
hooks.onFieldConfigMap.exec(fieldsConfigMap);
hooks.willCreateObjectType.exec(fieldsConfigMap);
return fieldsConfigMap;
};
return options;
};
function getType(_options = {}) {
const __options = {
fields: {},
name: objectId,
..._options
};
const {
name
} = __options;
if (graphqlTypesRegister.has(name)) {
return graphqlTypesRegister.get(name);
}
buildFields(__options, el => el.type());
const result = new _graphql.GraphQLObjectType(__options);
graphqlTypesRegister.set(__options.name, result);
return result;
}
function getInputType(_options = {}) {
const options = {
fields: {},
name: `${objectId}Input`,
..._options
};
const {
name
} = options;
if (graphqlTypesRegister.has(name)) {
return graphqlTypesRegister.get(name);
}
buildFields(options, el => el.inputType());
const result = new _graphql.GraphQLInputObjectType(options);
graphqlTypesRegister.set(name, result);
return result;
}
function interfaceType(_options = {}) {
const options = {
fields: {},
name: `${objectId}Interface`,
..._options
};
const {
name
} = options;
if (graphqlTypesRegister.has(name)) {
return graphqlTypesRegister.get(name);
}
buildFields(options, el => el.type());
const result = new _graphql.GraphQLInterfaceType(options);
graphqlTypesRegister.set(name, result);
return result;
}
function getSDL() {
const result = getType();
const object = new _graphql.GraphQLSchema({
types: [result]
});
return (0, _graphql.printSchema)(object);
}
function getInputSDL() {
const object = new _graphql.GraphQLSchema({
types: [getInputType()]
});
return (0, _graphql.printSchema)(object);
}
const parsed = {
getInputType,
getType,
inputToString: getInputSDL,
interfaceType,
object: objectInput,
typeToString: getSDL
};
Object.assign(graphqlParsed, parsed);
return graphqlParsed;
}
static fieldToGraphQL(init) {
const {
fieldName,
parentName
} = init;
const fieldClone = init.field;
const plainField = fieldClone.asFinalFieldDef;
const {
optional,
typeName
} = fieldClone;
const {
description
} = plainField;
const path = [...init.path, fieldName];
const cacheId = path.join('--');
if (resultsCache.has(cacheId)) {
return fieldsRegister.get(cacheId);
}
// save reference for circular dependencies
const fieldParsed = {};
fieldsRegister.set(cacheId, fieldParsed);
const subTypeName = (0, _parseTypeName.parseTypeName)({
field: fieldClone,
fieldName,
parentName
});
const self = this;
// @ts-ignore
const creators = {
ID() {
return {
inputType: () => _graphql.GraphQLID,
type: () => _graphql.GraphQLID
};
},
alias() {
return {}; // handled in object parser;
},
any() {
const create = wrapCreationWithCache('Any', () => new _graphql.GraphQLScalarType({
name: 'Any'
}));
return {
inputType: create,
type: create
};
},
array() {
const {
utils: {
listItemType: innerFieldType
}
} = fieldClone;
const id = (0, _parseTypeName.parseTypeName)({
field: innerFieldType,
fieldName,
parentName
});
const convertFieldResult = self.fieldToGraphQL({
field: innerFieldType,
fieldName,
parentName,
path
});
return {
inputType: wrapCreationWithCache(`${id}ListInput`, (...args) => {
return new _graphql.GraphQLList(convertFieldResult.inputType(...args));
}),
type: wrapCreationWithCache(`${id}List`, (...args) => {
return new _graphql.GraphQLList(convertFieldResult.type(...args));
})
};
},
boolean() {
return {
inputType: () => _graphql.GraphQLBoolean,
type: () => _graphql.GraphQLBoolean
};
},
cursor() {
const cursor = fieldClone;
return {
inputType: cursor.utils.object.graphqlInputType,
type: cursor.utils.object.graphqlType
};
},
date() {
return {
inputType: () => _GraphQLDateType.GraphQLDateType,
type: () => _GraphQLDateType.GraphQLDateType
};
},
email() {
return {
inputType: () => _graphql.GraphQLString,
type: () => _graphql.GraphQLString
};
},
enum() {
function createEnum(options) {
const values = {};
(0, _utils.assertEqual)(fieldClone.type, 'enum');
fieldClone.def.forEach(key => {
values[key] = {
value: key
};
});
return new _graphql.GraphQLEnumType({
name: subTypeName,
...options,
values
});
}
return {
inputType: wrapCreationWithCache(subTypeName, createEnum),
type: wrapCreationWithCache(subTypeName, createEnum)
};
},
float() {
return {
inputType: () => _graphql.GraphQLFloat,
type: () => _graphql.GraphQLFloat
};
},
int() {
return {
inputType: () => _graphql.GraphQLInt,
type: () => _graphql.GraphQLInt
};
},
literal() {
if (!_LiteralField.LiteralField.is(fieldClone)) throw new Error('ts');
const {
description,
def
} = fieldClone;
const recordName = (0, _parseTypeName.parseTypeName)({
field: fieldClone,
fieldName,
parentName
});
function createLiteral(options) {
return new _graphql.GraphQLScalarType({
...options,
description: JSON.stringify(description || `Literal value: ${def.value}`),
name: recordName,
parseValue(value) {
return _LiteralField.LiteralField.utils.deserialize({
...def,
value
});
},
serialize(value) {
return _LiteralField.LiteralField.utils.deserialize({
...def,
value
});
}
});
}
return {
inputType: wrapCreationWithCache(recordName, createLiteral),
type: wrapCreationWithCache(recordName, createLiteral)
};
},
meta() {
throw new Error('meta field');
},
null() {
return {
inputType: () => _GraphQLNullType.GraphQLNullType,
type: () => _GraphQLNullType.GraphQLNullType
};
},
object() {
(0, _utils.assertEqual)(fieldClone.type, 'object');
const id = (0, _parseTypeName.parseTypeName)({
field: fieldClone,
fieldName,
parentName
});
const def = _ObjectType.ObjectType.is(fieldClone.def) ? fieldClone.def.clone(el => el.def()) : fieldClone.def;
// @ts-ignore
const object = _ObjectType.ObjectType.getOrSet(id, def);
const res = self.objectToGraphQL({
object
});
return {
inputType: res.getInputType,
type: res.getType
};
},
phone() {
return {
inputType: () => new _GraphQLPhoneType.GraphQLPhoneType(),
type: () => new _GraphQLPhoneType.GraphQLPhoneType()
};
},
record() {
const recordName = (0, _parseTypeName.parseTypeName)({
field: fieldClone,
fieldName,
parentName
});
function createRecord(options) {
return new _graphql.GraphQLScalarType({
...options,
name: recordName,
parseValue(value) {
return fieldClone.parse(value);
},
serialize(value) {
return fieldClone.parse(value);
}
});
}
return {
inputType: wrapCreationWithCache(recordName, createRecord),
type: wrapCreationWithCache(recordName, createRecord)
};
},
string() {
return {
inputType: () => _graphql.GraphQLString,
type: () => _graphql.GraphQLString
};
},
ulid() {
return {
inputType: () => _GraphQLUlidType.GraphQLUlidType,
type: () => _GraphQLUlidType.GraphQLUlidType
};
},
undefined() {
const create = wrapCreationWithCache('Undefined', () => new _graphql.GraphQLScalarType({
name: 'Undefined'
}));
return {
inputType: create,
type: create
};
},
union() {
if (!_UnionField.UnionField.is(fieldClone)) throw fieldClone;
let descriptions = [];
// if all types are objects it can be used as normal GraphQL union
// otherwise we need to create a scalar, since GraphQL only accepts unions of objects
// - https://github.com/graphql/graphql-js/issues/207
// GraphQL union items cannot be used as input, so we always use scalars for inputs:
// - https://github.com/graphql/graphql-spec/issues/488
let areAllObjects = true;
fieldClone.utils.fieldTypes.forEach(field => {
if (field.type !== 'object') areAllObjects = false;
descriptions.push(describeField(field.definition));
});
let description = undefined;
descriptions = descriptions.map(el => el.trim());
description = descriptions.join(' | ');
if (description.length > 100) {
description = `Union of:\n${descriptions.map(el => ` - ${el}`).join('\n')}`;
} else {
description = `Union of ${descriptions.join(' | ')}`.trim();
}
description = description.replace(/ /g, ' ');
const scalarUnion = new _graphql.GraphQLScalarType({
description,
name: subTypeName,
parseValue(value) {
return fieldClone.parse(value);
},
serialize(value) {
return fieldClone.parse(value);
}
});
return {
inputType: wrapCreationWithCache(subTypeName, () => {
return scalarUnion;
}),
type: wrapCreationWithCache(subTypeName, (...options) => {
if (!areAllObjects) return scalarUnion;
return new _graphql.GraphQLUnionType({
name: subTypeName,
...options,
types: fieldClone.utils.fieldTypes.map((field, index) => {
if (!_ObjectField.ObjectField.is(field)) throw field;
let object = field.utils.object;
if (!object.id) {
object = object.clone(el => el.objectType(`${subTypeName}_${index}`));
}
return object.graphqlType();
})
});
})
};
},
unknown() {
const GraphQLUnknownType = new _graphql.GraphQLScalarType({
name: subTypeName
});
return {
inputType: () => GraphQLUnknownType,
type: () => GraphQLUnknownType
};
}
};
const result = {
description,
fieldName,
inputType(...args) {
if (typeName === 'alias') {
// dev only assertion, aliasing should be handled in parseObject
throw new Error(`can't handle alias in convertField.`);
}
let _result = creators[typeName]().inputType(...args);
if (fieldClone.list) {
_result = new _graphql.GraphQLList(_result);
}
if (!optional && fieldClone.defaultValue === undefined) {
_result = new _graphql.GraphQLNonNull(_result);
}
return _result;
},
plainField,
type(...args) {
if (typeName === 'alias') {
// dev only assertion, aliasing should be handled in parseObject
throw new Error(`can't handle alias in convertField.`);
}
let _result = creators[typeName]().type(...args);
if (fieldClone.list) {
_result = new _graphql.GraphQLList(_result);
}
if (!optional && (0, _graphql.isNullableType)(_result)) {
_result = new _graphql.GraphQLNonNull(_result);
}
return _result;
},
typeName
};
Object.assign(fieldParsed, result);
return result;
}
}
exports.GraphQLParser = GraphQLParser;
function describeField(field) {
if (field.name) return field.name;
if (field.type === 'literal') {
const value = _utils.BJSON.stringify(_LiteralField.LiteralField.utils.deserialize(field.def), {
quoteKeys(key) {
return ` ${key}`;
}
});
return `${value}`;
}
return _utils.BJSON.stringify(field, {
handler(payload) {
const {
value
} = payload;
if ((value === null || value === void 0 ? void 0 : value.type) === 'object' && value.def) {
const meta = (0, _MetaFieldField.getObjectDefinitionMetaField)((value === null || value === void 0 ? void 0 : value.def) || {});
if (meta !== null && meta !== void 0 && meta.def.id) return meta.def.id;
return describeField((0, _MetaFieldField.cleanMetaField)(value.def));
}
return undefined;
},
quoteKeys(key) {
return ` ${key}`;
},
quoteValues(value, {
key
}) {
if (value === false) return '';
if (key === 'type') return ` ${value} `;
return `${value}`;
}
});
}
//# sourceMappingURL=GraphQLParser.js.map