@backland/schema
Version:
TypeScript schema declaration and validation library with static type inference
474 lines (463 loc) • 14.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _exportNames = {
ObjectType: true,
BacklandObject: true,
createObjectType: true,
createBacklandObject: true,
createSchema: true,
resetTypesCache: true
};
exports.createBacklandObject = exports.ObjectType = exports.BacklandObject = void 0;
exports.createObjectType = createObjectType;
exports.resetTypesCache = exports.createSchema = void 0;
var _utils = require("@backland/utils");
var _CircularDeps = require("./CircularDeps");
var _applyValidator = require("./applyValidator");
var _extendObjectDefinition = require("./extendObjectDefinition");
var _MetaFieldField = require("./fields/MetaFieldField");
var _fieldDefinitions = require("./fields/_fieldDefinitions");
Object.keys(_fieldDefinitions).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _fieldDefinitions[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _fieldDefinitions[key];
}
});
});
var _getObjectErrors = require("./getObjectErrors");
var _getObjectHelpers = require("./getObjectHelpers");
var _implementObject = require("./implementObject");
Object.keys(_implementObject).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _implementObject[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _implementObject[key];
}
});
});
var _objectInferenceUtils = require("./objectInferenceUtils");
Object.keys(_objectInferenceUtils).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _objectInferenceUtils[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _objectInferenceUtils[key];
}
});
});
var _parseObjectDefinition = require("./parseObjectDefinition");
Object.keys(_parseObjectDefinition).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _parseObjectDefinition[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _parseObjectDefinition[key];
}
});
});
var _withCache = require("./withCache");
var _parseFields = require("./fields/_parseFields");
Object.keys(_parseFields).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _parseFields[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _parseFields[key];
}
});
});
class ObjectType {
get __isBacklandObject() {
return true;
}
static __isBacklandObject = true;
constructor(objectDef) {
this.inputDefinition = objectDef;
this.__withCache = (0, _withCache.withCache)(this);
}
get definition() {
return this.__definitionCache = this.__definitionCache || (() => {
const objectDef = typeof this.inputDefinition === 'function' ? this.inputDefinition(_CircularDeps.CircularDeps) : this.inputDefinition;
if (!objectDef || typeof objectDef !== 'object') {
throw new Error('Expected object definition to be an object');
}
return (0, _parseObjectDefinition.parseObjectDefinition)(objectDef).definition;
})();
}
get description() {
return this.meta.description;
}
__hidden = false;
set hidden(value) {
this.__hidden = value;
}
get hidden() {
return this.__hidden;
}
// definition without metadata (name, etc)
cleanDefinition() {
// @ts-ignore
return (0, _MetaFieldField.cleanMetaField)(this.clone(el => el.def()));
}
edit() {
return (0, _extendObjectDefinition.extendObjectDefinition)(this);
}
get meta() {
// @ts-ignore
return this.definition[_MetaFieldField.objectMetaFieldKey].def;
}
__setMetaData(k, value) {
// @ts-ignore
this.definition[_MetaFieldField.objectMetaFieldKey].def[k] = value;
}
parse(input, options) {
const {
customMessage,
customErrorMessage
} = options || {};
const {
errors,
parsed
} = this.safeParse(input, options);
if (errors.length) {
let e_message = errors.join(' \n');
if (this.id) {
e_message = `${this.id}: ${e_message}`;
}
const err = (0, _applyValidator.parseValidationError)(input, customMessage || customErrorMessage, e_message);
err.isObjectValidationError = true;
err.fieldErrors = errors;
throw err;
}
return parsed;
}
softParse = (input, options = {}) => {
return this.parse(input, {
...options,
allowExtraFields: true
});
};
validate(input) {
try {
this.parse(input);
return true;
} catch (e) {
return false;
}
}
safeParse(input, options) {
const {
partial = false,
excludeInvalidListItems,
includeHidden = true,
allowExtraFields,
exclude
} = options || {};
const objectDef = {
...this.definition
};
if (this.__hidden && !includeHidden) return {
errors: [],
parsed: {}
};
const errors = [];
const parsed = {};
const fieldInputsList = [];
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new _utils.RuntimeError(`Invalid input. Expected object, found ${(0, _utils.getTypeName)(input)}`, {
input
});
}
input = {
...input
};
const inputKeys = (value => {
if (exclude) {
return value.filter(el => !exclude.includes(el));
}
return value;
})(Object.keys(input));
// @ts-ignore
let fields = (options === null || options === void 0 ? void 0 : options.fields) || Object.keys(this.definition);
// === Start handling {[K: string}: any}|{[K: number}: any} ===
const anyStringKey = fields.find(field => field === _fieldDefinitions.SpecialObjectKeyEnum.$string);
const anyNumberKey = fields.find(field => field === _fieldDefinitions.SpecialObjectKeyEnum.$number);
if (anyNumberKey || anyStringKey) {
const allFieldsSet = new Set(fields);
const keysNotDefined = inputKeys.filter(k => !allFieldsSet.has(k));
fields = fields.filter(k => !_fieldDefinitions.SpecialObjectKeyEnum.list.includes(k));
if (anyStringKey) {
const def = objectDef[anyStringKey];
keysNotDefined.forEach(key => {
objectDef[key] = def;
});
} else if (anyNumberKey) {
const def = objectDef[anyNumberKey];
keysNotDefined.forEach(key => {
if (!key.match(/\d*/)) return;
objectDef[key] = def;
});
}
}
// === End handling {[K: string}: any}|{[K: number}: any} ===
fields.forEach(currField => {
var _fieldDef$def;
if (currField.startsWith('$')) return; // special field
if ((0, _MetaFieldField.isMetaFieldKey)(currField)) return;
if (exclude && exclude.includes(currField)) return;
// @ts-ignore
const fieldDef = objectDef[currField];
if (!includeHidden && fieldDef.hidden) return;
if (fieldDef.type === 'alias') {
const instance = (0, _parseObjectDefinition.__getCachedFieldInstance)(fieldDef);
return fieldInputsList.push({
composer: instance.composer,
fieldDef,
key: currField,
value: undefined
});
}
const value = input[currField];
const hasAutoCreateOption = (fieldDef === null || fieldDef === void 0 ? void 0 : (_fieldDef$def = fieldDef.def) === null || _fieldDef$def === void 0 ? void 0 : _fieldDef$def['autoCreate']) === true;
if ((value === undefined || value === null) && partial && !hasAutoCreateOption) {
return;
}
fieldInputsList.push({
composer: undefined,
fieldDef,
key: currField,
value
});
});
// parsing ignoring aliases
const notAliasFieldsResults = fieldInputsList.map(entry => {
const {
key,
fieldDef,
value
} = entry;
const result = (0, _getObjectErrors.validateObjectFields)({
definition: fieldDef,
fieldName: key,
fieldParserOptions: {
excludeInvalidListItems
},
value
});
if (result.parsed !== undefined) {
parsed[key] = result.parsed;
}
if (!entry.composer) {
errors.unshift(...result.errors);
}
return {
...entry,
...result
};
});
// handling aliases
notAliasFieldsResults.forEach(field => {
let {
key,
composer
} = field;
if (!composer) return;
const value = composer.compose(parsed);
const fieldDef = composer.def;
const result = (0, _getObjectErrors.validateObjectFields)({
definition: fieldDef,
fieldName: key,
fieldParserOptions: {
excludeInvalidListItems
},
value
});
if (result.parsed !== undefined) {
parsed[key] = result.parsed;
}
errors.unshift(...result.errors);
});
const resulting = allowExtraFields ? {
...input,
...parsed
} : parsed;
return {
errors,
parsed: resulting
};
}
describe(...descriptions) {
if (descriptions.length === 1 && typeof descriptions[0] === 'string') {
this.__setMetaData('description', descriptions[0]);
return this;
}
const commentsConfig = descriptions[0];
(0, _utils.invariantType)({
commentsConfig
}, 'object', {
commentsConfig
});
const definition = this.definition;
Object.entries(commentsConfig).forEach(([name, comment]) => {
(0, _utils.invariantType)({
[name]: definition[name]
}, 'object', `"${name}" is not in object definition.`);
definition[name].description = comment || '';
});
return this;
}
clone(handler) {
const parsed = (0, _parseObjectDefinition.parseField)(this);
const input = (0, _extendObjectDefinition.extendObjectDefinition)(parsed);
return handler(input);
}
get id() {
return this.meta.id;
}
get nonNullId() {
const id = this.meta.id;
if (!id) {
throw new _utils.RuntimeError('Expected object to be identified.', {
definition: this.definition
});
}
return id;
}
identify(id) {
if (id && id === this.id) return this;
if (this.id) {
throw new Error(`Trying to replace existing id "${this.id}" with "${id}". You can clone it to create a new Object.`);
}
(0, _utils.expectedType)({
id
}, 'string', 'truthy');
this.__setMetaData('id', id);
ObjectType.register.set(id, this);
return this;
}
helpers = () => {
return this.__withCache('helpers', () => (0, _getObjectHelpers.getObjectHelpers)(this));
};
toGraphQL = name => {
if (name) {
this.identify(name);
}
if (!this.id) {
throw new _utils.RuntimeError('Should object.identify() before converting to Graphql.' + '\nYou can call object.clone() to choose a different identification.', {
'used definition': this.definition
});
}
// @ts-ignore circular
const {
GraphQLParser
} = _CircularDeps.CircularDeps.GraphQLParser;
return GraphQLParser.objectToGraphQL({
object: this
});
};
graphqlType = options => {
return this.toGraphQL().getType(options);
};
graphqlInterfaceType = options => {
return this.toGraphQL().interfaceType(options);
};
graphqlPrint = () => {
return this.toGraphQL().typeToString();
};
typescriptPrint = options => {
// @ts-ignore circular
return _CircularDeps.CircularDeps.objectToTypescript(this.nonNullId,
// @ts-ignore
this, options);
};
graphqlTypeToString = () => {
return this.toGraphQL().typeToString();
};
graphqlInputType = options => {
return this.toGraphQL().getInputType(options);
};
implement = (name, ...parents) => {
return (0, _implementObject.implementObject)(name, this.definition, ...parents);
};
static async reset() {
ObjectType.register.clear();
const promises = [];
try {
// only available server side or in tests
const {
GraphQLParser,
GraphType
} = _CircularDeps.CircularDeps;
promises.push(GraphQLParser.reset(), GraphType.reset());
} catch (e) {
if (typeof window === 'undefined') {
throw e;
}
}
await Promise.all(promises);
}
static register = (0, _utils.createStore)();
/**
* Get an Object with the provided id
* or set a new Object in the register if not found.
* @param id
* @param def
*/
static getOrSet = (id, def) => {
const existing = ObjectType.register.has(id) && ObjectType.register.get(id);
if (existing) {
return existing;
}
// @ts-ignore
return new ObjectType(() => {
def = typeof def === 'function' ? def() : def;
return def;
}).identify(id);
};
graphQLMiddleware = [];
addGraphQLMiddleware = middleware => {
this.graphQLMiddleware.push(...(0, _utils.ensureArray)(middleware));
};
static is(input) {
return (0, _objectInferenceUtils.isObject)(input);
}
}
exports.ObjectType = ObjectType;
const BacklandObject = ObjectType;
exports.BacklandObject = BacklandObject;
function createObjectType(...args) {
var _getObjectDefinitionM, _getObjectDefinitionM2;
const fields = args.length === 2 ? args[1] : args[0];
const id = args.length === 2 ? args[0] : undefined;
if (id) {
// @ts-ignore
return ObjectType.getOrSet(id, fields);
}
const idFromDefinition = (_getObjectDefinitionM = (0, _MetaFieldField.getObjectDefinitionMetaField)(fields)) === null || _getObjectDefinitionM === void 0 ? void 0 : (_getObjectDefinitionM2 = _getObjectDefinitionM.def) === null || _getObjectDefinitionM2 === void 0 ? void 0 : _getObjectDefinitionM2.id;
if (idFromDefinition) {
return ObjectType.getOrSet(idFromDefinition, fields);
}
return new ObjectType(fields);
}
const createBacklandObject = createObjectType;
exports.createBacklandObject = createBacklandObject;
const createSchema = createObjectType;
exports.createSchema = createSchema;
const resetTypesCache = ObjectType.reset;
exports.resetTypesCache = resetTypesCache;
//# sourceMappingURL=ObjectType.js.map