UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

151 lines (150 loc) 6.26 kB
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 }); }