shelving
Version:
Toolkit for using data in JavaScript.
151 lines (150 loc) • 6.26 kB
JavaScript
import { ValueError } from "../error/ValueError.js";
import { Feedback } from "../feedback/Feedback.js";
import { KEY } from "../schema/KeySchema.js";
import { updateData } from "../util/update.js";
import { validateData } from "../util/validate.js";
import { AsyncThroughProvider, ThroughProvider } from "./ThroughProvider.js";
/** Validate a synchronous source provider (source can have any type because validation guarantees the type). */
export class ValidationProvider extends ThroughProvider {
schemas;
constructor(schemas, source) {
super(source);
this.schemas = schemas;
}
/** Get a named schema. */
getSchema(collection) {
return this.schemas[collection];
}
getItem(collection, id) {
return _validateItem(collection, super.getItem(collection, id), this.getSchema(collection), this.getItem);
}
async *getItemSequence(collection, id) {
const schema = this.getSchema(collection);
for await (const item of super.getItemSequence(collection, id))
yield _validateItem(collection, item, schema, this.getItemSequence);
}
addItem(collection, data) {
return super.addItem(collection, validateData(data, this.getSchema(collection).props));
}
setItem(collection, id, data) {
super.setItem(collection, id, validateData(data, this.getSchema(collection).props));
}
updateItem(collection, id, updates) {
validateData(updateData({}, updates), this.getSchema(collection).props, true);
super.updateItem(collection, id, updates);
}
deleteItem(collection, id) {
super.deleteItem(collection, id);
}
countQuery(collection, query) {
return super.countQuery(collection, query);
}
getQuery(collection, query) {
return _validateItems(collection, super.getQuery(collection, query), this.getSchema(collection), this.getQuery);
}
setQuery(collection, query, data) {
super.setQuery(collection, query, this.getSchema(collection).validate(data));
}
updateQuery(collection, query, updates) {
validateData(updateData({}, updates), this.getSchema(collection).props, true);
super.updateQuery(collection, query, updates);
}
deleteQuery(collection, query) {
super.deleteQuery(collection, query);
}
async *getQuerySequence(collection, query) {
const schema = this.getSchema(collection);
for await (const items of super.getQuerySequence(collection, query))
yield _validateItems(collection, items, schema, this.getQuerySequence);
}
}
/** Validate an asynchronous source provider (source can have any type because validation guarantees the type). */
export class AsyncValidationProvider extends AsyncThroughProvider {
schemas;
constructor(schemas, source) {
super(source);
this.schemas = schemas;
}
/** Get a named data schema for this database. */
getSchema(collection) {
return this.schemas[collection];
}
async getItem(collection, id) {
return _validateItem(collection, await super.getItem(collection, id), this.getSchema(collection), this.getItem);
}
async *getItemSequence(collection, id) {
const schema = this.getSchema(collection);
for await (const item of super.getItemSequence(collection, id))
yield _validateItem(collection, item, schema, this.getItemSequence);
}
addItem(collection, data) {
return super.addItem(collection, validateData(data, this.getSchema(collection).props));
}
setItem(collection, id, data) {
return super.setItem(collection, id, validateData(data, this.getSchema(collection).props));
}
updateItem(collection, id, updates) {
validateData(updateData({}, updates), this.getSchema(collection).props, true);
return super.updateItem(collection, id, updates);
}
deleteItem(collection, id) {
return super.deleteItem(collection, id);
}
countQuery(collection, query) {
return super.countQuery(collection, query);
}
async getQuery(collection, query) {
return _validateItems(collection, await super.getQuery(collection, query), this.getSchema(collection), this.getQuery);
}
async *getQuerySequence(collection, query) {
const schema = this.getSchema(collection);
for await (const items of super.getQuerySequence(collection, query))
yield _validateItems(collection, items, schema, this.getQuerySequence);
}
setQuery(collection, query, data) {
return super.setQuery(collection, query, validateData(data, this.getSchema(collection).props));
}
updateQuery(collection, query, updates) {
validateData(updateData({}, updates), this.getSchema(collection).props, true);
return super.updateQuery(collection, query, updates);
}
deleteQuery(collection, query) {
return super.deleteQuery(collection, query);
}
}
function _validateItem(collection, item, schema, caller) {
if (!item)
return undefined;
try {
return validateData(item, { id: KEY, ...schema.props });
}
catch (thrown) {
if (!(thrown instanceof Feedback))
throw thrown;
throw new ValueError(`Invalid data for "${collection}"`, { collection, item, cause: thrown, caller });
}
}
/**
* Validate a set of entities for this query reference.
* @throws `ValueError` if one or more items did not validate (conflict because the program is not in an expected state).
*/
function _validateItems(collection, items, schema, caller) {
return Array.from(_yieldValidItems(collection, items, { id: KEY, ...schema.props }, caller));
}
function* _yieldValidItems(collection, items, validators, caller) {
let invalid = false;
const feedbacks = {};
for (const item of items) {
try {
yield validateData(item, validators);
}
catch (thrown) {
if (!(thrown instanceof Feedback))
throw thrown;
invalid = true;
feedbacks[item.id] = thrown;
}
}
if (invalid)
throw new ValueError(`Invalid data for "${collection}"`, { collection, items, cause: feedbacks, caller });
}