tinybase
Version:
A reactive data store and sync engine.
164 lines (156 loc) • 5.23 kB
JavaScript
const getTypeOf = (thing) => typeof thing;
const EMPTY_STRING = '';
const STRING = getTypeOf(EMPTY_STRING);
const BOOLEAN = getTypeOf(true);
const NUMBER = getTypeOf(0);
const TYPE = 'type';
const DEFAULT = 'default';
const ALLOW_NULL = 'allowNull';
const _VALUE = 'value';
const DOMAIN = 'domain';
const KEY = 'key';
const OPTIONAL = 'optional';
const REQUIRED = 'required';
const UNIT = 'unit';
const getIfNotFunction = (predicate) => (value, then, otherwise) =>
predicate(value) ? otherwise?.() : then(value);
const isNullish = (thing) => thing == null;
const isUndefined = (thing) => thing === void 0;
const isNull = (thing) => thing === null;
const isTrue = (thing) => thing === true;
const isFalse = (thing) => thing === false;
const ifNotNullish = getIfNotFunction(isNullish);
const ifNotUndefined = getIfNotFunction(isUndefined);
const isArray = (thing) => Array.isArray(thing);
const size = (arrayOrString) => arrayOrString.length;
const arrayEvery = (array, cb) => array.every(cb);
const arrayForEach = (array, cb) => array.forEach(cb);
const arrayFind = (array, cb) => array.find(cb);
const object = Object;
const getPrototypeOf = (obj) => object.getPrototypeOf(obj);
const objEntries = object.entries;
const isObject = (obj) =>
!isNullish(obj) &&
ifNotNullish(
getPrototypeOf(obj),
(objPrototype) =>
objPrototype == object.prototype ||
isNullish(getPrototypeOf(objPrototype)),
/* istanbul ignore next */
() => true,
);
const objIds = object.keys;
const objFreeze = object.freeze;
const objNew = (entries = []) => object.fromEntries(entries);
const objForEach = (obj, cb) =>
arrayForEach(objEntries(obj), ([id, value]) => cb(value, id));
const objSize = (obj) => size(objIds(obj));
const objIsEmpty = (obj) => isObject(obj) && objSize(obj) == 0;
const createCustomSchematizer = (unwrapSchema, getProperties) => {
const toCellOrValueSchema = (schema) => {
const [unwrapped, defaultValue, allowNull] = unwrapSchema(schema);
const type = unwrapped?.type;
if (type !== STRING && type !== NUMBER && type !== BOOLEAN) {
return void 0;
}
const cellOrValueSchema = {[TYPE]: type};
ifNotUndefined(defaultValue, (defaultValue2) => {
cellOrValueSchema[DEFAULT] = defaultValue2;
});
if (allowNull) {
cellOrValueSchema[ALLOW_NULL] = true;
}
return cellOrValueSchema;
};
const toTablesSchema = (schemas) => {
const tablesSchema = objNew();
objForEach(schemas, (schema, tableId) => {
const tableSchema = objNew();
ifNotUndefined(getProperties(schema), (properties) =>
objForEach(properties, (cellSchema, cellId) =>
ifNotUndefined(toCellOrValueSchema(cellSchema), (cellSchema2) => {
tableSchema[cellId] = cellSchema2;
}),
),
);
if (!objIsEmpty(tableSchema)) {
tablesSchema[tableId] = tableSchema;
}
});
return tablesSchema;
};
const toValuesSchema = (schemas) => {
const valuesSchema = objNew();
objForEach(schemas, (schema, valueId) =>
ifNotUndefined(toCellOrValueSchema(schema), (valueSchema) => {
valuesSchema[valueId] = valueSchema;
}),
);
return valuesSchema;
};
return objFreeze({
toTablesSchema,
toValuesSchema,
});
};
const unwrapSchema = (schema, defaultValue, allowNull) => {
if (isArray(schema)) {
const hasNull = !arrayEvery(
schema,
(item) => !isNull(item?.[UNIT]) && !isNull(item),
);
if (
size(schema) === 2 &&
isFalse(schema[0]?.[UNIT]) &&
isTrue(schema[1]?.[UNIT])
) {
return [{[TYPE]: BOOLEAN}, defaultValue, allowNull ?? false];
}
if (hasNull) {
const nonNullItem = arrayFind(
schema,
(item) => !isNull(item?.[UNIT]) && !isNull(item) && item !== '=',
);
if (nonNullItem) {
return unwrapSchema(nonNullItem, defaultValue, true);
}
}
}
return [
{[TYPE]: schema?.[DOMAIN] || schema},
defaultValue,
allowNull ?? false,
];
};
const getProperties = (schema) => {
const properties = objNew();
const schemaData = schema?.json ?? schema;
if (schemaData?.[REQUIRED]) {
arrayForEach(schemaData[REQUIRED], (field) => {
properties[field[KEY]] = field[_VALUE];
});
}
if (schemaData?.[OPTIONAL]) {
arrayForEach(schemaData[OPTIONAL], (field) => {
const value = field[_VALUE];
const defaultVal = field[DEFAULT];
properties[field[KEY]] = !isUndefined(defaultVal)
? {[_VALUE]: value, [DEFAULT]: defaultVal}
: value;
});
}
return objIsEmpty(properties) ? void 0 : properties;
};
const unwrapSchemaWithDefaults = (schema, defaultValue, allowNull) => {
if (isArray(schema) && size(schema) === 3 && schema[1] === '=') {
const schemaValue = schema[0]?.json ?? schema[0];
return unwrapSchema(schemaValue, schema[2], allowNull);
}
if (schema?.[_VALUE] && !isUndefined(schema?.[DEFAULT])) {
return unwrapSchema(schema[_VALUE], schema[DEFAULT], allowNull);
}
return unwrapSchema(schema, defaultValue, allowNull);
};
const createArkTypeSchematizer = () =>
createCustomSchematizer(unwrapSchemaWithDefaults, getProperties);
export {createArkTypeSchematizer};