@instantdb/core
Version:
Instant's core local abstraction
137 lines • 6.12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateTransactions = exports.TransactionValidationError = exports.isValidEntityId = void 0;
const instatx_ts_1 = require("./instatx.js");
const uuid_1 = require("uuid");
const isValidEntityId = (value) => {
if (typeof value !== 'string') {
return false;
}
if ((0, instatx_ts_1.isLookup)(value)) {
return true;
}
return (0, uuid_1.validate)(value);
};
exports.isValidEntityId = isValidEntityId;
class TransactionValidationError extends Error {
constructor(message) {
super(message);
this.name = 'TransactionValidationError';
}
}
exports.TransactionValidationError = TransactionValidationError;
const formatAvailableOptions = (items) => items.length > 0 ? items.join(', ') : 'none';
const createEntityNotFoundError = (entityName, availableEntities) => new TransactionValidationError(`Entity '${entityName}' does not exist in schema. Available entities: ${formatAvailableOptions(availableEntities)}`);
const TYPE_VALIDATORS = {
string: (value) => typeof value === 'string',
number: (value) => typeof value === 'number' && !isNaN(value),
boolean: (value) => typeof value === 'boolean',
date: (value) => value instanceof Date ||
typeof value === 'string' ||
typeof value === 'number',
json: () => true,
};
const isValidValueForAttr = (value, attrDef) => {
if (value === null || value === undefined)
return true;
return TYPE_VALIDATORS[attrDef.valueType]?.(value) ?? false;
};
const validateEntityExists = (entityName, schema) => {
const entityDef = schema.entities[entityName];
if (!entityDef) {
throw createEntityNotFoundError(entityName, Object.keys(schema.entities));
}
return entityDef;
};
const validateDataOperation = (entityName, data, schema) => {
const entityDef = validateEntityExists(entityName, schema);
if (typeof data !== 'object' || data === null) {
throw new TransactionValidationError(`Arguments for data operation on entity '${entityName}' must be an object, but received: ${typeof data}`);
}
for (const [attrName, value] of Object.entries(data)) {
if (attrName === 'id')
continue; // id is handled specially
const attrDef = entityDef.attrs[attrName];
if (attrDef) {
if (!isValidValueForAttr(value, attrDef)) {
throw new TransactionValidationError(`Invalid value for attribute '${attrName}' in entity '${entityName}'. Expected ${attrDef.valueType}, but received: ${typeof value}`);
}
}
}
};
const validateLinkOperation = (entityName, links, schema) => {
const entityDef = validateEntityExists(entityName, schema);
if (typeof links !== 'object' || links === null) {
throw new TransactionValidationError(`Arguments for link operation on entity '${entityName}' must be an object, but received: ${typeof links}`);
}
for (const [linkName, linkValue] of Object.entries(links)) {
const link = entityDef.links[linkName];
if (!link) {
const availableLinks = Object.keys(entityDef.links);
throw new TransactionValidationError(`Link '${linkName}' does not exist on entity '${entityName}'. Available links: ${formatAvailableOptions(availableLinks)}`);
}
// Validate UUID format for link values
if (linkValue !== null && linkValue !== undefined) {
if (Array.isArray(linkValue)) {
// Handle array of UUIDs
for (const linkReference of linkValue) {
if (!(0, exports.isValidEntityId)(linkReference)) {
throw new TransactionValidationError(`Invalid entity ID in link '${linkName}' for entity '${entityName}'. Expected a UUID or a lookup, but received: ${linkReference}`);
}
}
}
else {
// Handle single UUID
if (!(0, exports.isValidEntityId)(linkValue)) {
throw new TransactionValidationError(`Invalid UUID in link '${linkName}' for entity '${entityName}'. Expected a UUID, but received: ${linkValue}`);
}
}
}
}
};
const VALIDATION_STRATEGIES = {
create: validateDataOperation,
update: validateDataOperation,
merge: validateDataOperation,
link: validateLinkOperation,
unlink: validateLinkOperation,
delete: () => { },
};
const validateOp = (op, schema) => {
if (!schema)
return;
const [action, entityName, _id, args] = op;
// _id should be a uuid
if (!Array.isArray(_id)) {
const isUuid = (0, uuid_1.validate)(_id);
if (!isUuid) {
throw new TransactionValidationError(`Invalid id for entity '${entityName}'. Expected a UUID, but received: ${_id}`);
}
}
if (typeof entityName !== 'string') {
throw new TransactionValidationError(`Entity name must be a string, but received: ${typeof entityName}`);
}
const validator = VALIDATION_STRATEGIES[action];
if (validator && args !== undefined) {
validator(entityName, args, schema);
}
};
const validateTransactions = (inputChunks, schema) => {
const chunks = Array.isArray(inputChunks) ? inputChunks : [inputChunks];
for (const txStep of chunks) {
if (!txStep || typeof txStep !== 'object') {
throw new TransactionValidationError(`Transaction chunk must be an object, but received: ${typeof txStep}`);
}
if (!Array.isArray(txStep.__ops)) {
throw new TransactionValidationError(`Transaction chunk must have __ops array, but received: ${typeof txStep.__ops}`);
}
for (const op of txStep.__ops) {
if (!Array.isArray(op)) {
throw new TransactionValidationError(`Transaction operation must be an array, but received: ${typeof op}`);
}
validateOp(op, schema);
}
}
};
exports.validateTransactions = validateTransactions;
//# sourceMappingURL=transactionValidation.js.map