@getanthill/datastore
Version:
Event-Sourced Datastore
578 lines (577 loc) • 20.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = exports.updateMany = exports.entity = exports.events = exports.count = exports.restore = exports.patch = exports.update = exports.create = exports.get = exports.find = void 0;
const commander_1 = require("commander");
const ajv_1 = __importDefault(require("ajv"));
const ajv_formats_1 = __importDefault(require("ajv-formats"));
const merge_1 = __importDefault(require("lodash/merge"));
const omit_1 = __importDefault(require("lodash/omit"));
const pick_1 = __importDefault(require("lodash/pick"));
const utils = __importStar(require("./utils"));
const constants_1 = require("../constants");
const validator = new ajv_1.default({
useDefaults: false,
coerceTypes: true,
strict: false,
});
// @ts-ignore
(0, ajv_formats_1.default)(validator);
function addSchemaFields(h, { correlation_field, schema, }) {
h.option(`--${correlation_field} <${correlation_field}...>`, 'Correlation field');
h.option(`--json <json>`, 'JSON Query', JSON.parse);
for (const field in schema.model.properties) {
const { type, description } = schema.model.properties[field];
if (type === 'array') {
h.option(`--${field} <${field}...>`, description);
}
else {
h.option(`--${field} <${field}>`, description);
}
}
}
function buildQuery(config, cmd, schema) {
const { debug, dryRun, json, ...args } = cmd;
const query = (0, merge_1.default)((0, pick_1.default)(args, Object.keys({
[config.correlation_field]: {
type: 'string',
description: 'Correlation field',
},
...schema.properties,
})), json);
const isValid = validator.validate((0, omit_1.default)(schema, 'required'), query);
if (debug === true) {
utils.log({ dryRun, args, json, query, isValid }, cmd.format);
}
if (cmd.skipValidation === false && !isValid) {
utils.log({
err: 'Query is invalid',
dryRun,
args,
json,
query,
isValid,
details: validator.errors,
}, cmd.format);
throw new Error('Query is invalid');
}
return query;
}
function find(services, config) {
const model = config.name;
return async (cmd) => {
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
const { page, pageSize, ...args } = cmd;
const query = buildQuery(config, args, config.schema.model);
query._must_hash = cmd.skipHash === false;
if (cmd.fields) {
query._fields = cmd.fields.reduce((s, c) => ({
...s,
[c]: 1,
}), {});
}
if (cmd.sort) {
query._sort = cmd.sort.reduce((s, c) => ({
[c.replace(/^[+-]/, '')]: c[0] === '-' ? -1 : 1,
}), {});
}
if (cmd.dryRun === true) {
utils.log(query, cmd.format);
return;
}
let entities = [];
if (cmd.source === 'entities') {
const { data } = await datastore.find(model, query, page, pageSize, {
// @ts-ignore
'with-response-validation': `${cmd.skipValidation === false}`,
});
entities = data;
}
else {
const { data } = await datastore.allEvents(model, query, page, pageSize, {});
entities = data;
}
if (cmd.decrypt === true) {
const { data: decryptedData } = await datastore.decrypt(model, entities);
entities = decryptedData;
}
utils.log(entities, cmd.format);
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.find = find;
function get(services, config) {
const model = config.name;
return async (correlationId, cmd) => {
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
if (cmd.dryRun === true) {
utils.log({
[config.correlation_field]: correlationId,
}, cmd.format);
return;
}
let entity;
const { data: obj } = await datastore.get(model, correlationId);
entity = obj;
if (cmd.decrypt === true) {
const { data: [decrypted], } = await datastore.decrypt(model, [entity]);
entity = decrypted;
}
utils.log(entity, cmd.format);
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.get = get;
function create(services, config) {
const model = config.name;
const schema = config.schema;
return async (cmd) => {
const v = Object.keys(schema.events.CREATED).sort().pop();
if (!v) {
return;
}
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
const payload = buildQuery(config, cmd, schema.events.CREATED[v]);
if (cmd.dryRun === false) {
const { data } = await datastore.create(model, payload);
utils.log(data, cmd.format);
}
else {
utils.log(payload, cmd.format);
}
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.create = create;
function update(services, config) {
const model = config.name;
const schema = config.schema;
return async (correlationId, cmd) => {
const v = Object.keys(schema.events.CREATED).sort().pop();
if (!v) {
return;
}
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
const payload = buildQuery(config, cmd, schema.events.UPDATED[v]);
if (cmd.dryRun === false) {
// @ts-ignore
const { data } = await datastore.update(model, correlationId, payload, {
upsert: cmd.upsert,
});
utils.log(data, cmd.format);
}
else {
utils.log(payload, cmd.format);
}
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.update = update;
function patch(services, config) {
const model = config.name;
return async (correlationId, _patches, cmd) => {
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
const patches = _patches.map((p) => JSON.parse(p));
if (cmd.dryRun === false) {
const { data } = await datastore.patch(model, correlationId, patches);
utils.log(data, cmd.format);
}
else {
utils.log(patches, cmd.format);
}
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.patch = patch;
function restore(services, config) {
const model = config.name;
return async (correlationId, version, cmd) => {
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
if (cmd.dryRun === false) {
const { data } = await datastore.restore(model, correlationId, version);
utils.log(data, cmd.format);
}
else {
utils.log({ correlation_id: correlationId, version }, cmd.format);
}
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.restore = restore;
function count(services, config) {
const model = config.name;
return async (cmd) => {
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
const { page, pageSize, ...args } = cmd;
const query = buildQuery(config, args, config.schema.model);
if (cmd.dryRun === false) {
const c = await datastore.count(model, {
...query,
_must_hash: cmd.skipHash === false,
}, cmd.source);
utils.log(c, cmd.format);
}
else {
utils.log(query, cmd.format);
}
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.count = count;
function events(services, config) {
const model = config.name;
return async (correlationId, cmd) => {
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
const { page, pageSize } = cmd;
if (cmd.dryRun === true) {
utils.log({
[config.correlation_field]: correlationId,
}, cmd.format);
return;
}
let events = [];
const { data } = await datastore.events(model, correlationId, page, pageSize);
events = data;
if (cmd.decrypt === true) {
const { data: decryptedEvents } = await datastore.decrypt(model, events);
events = decryptedEvents;
}
utils.log(events, cmd.format);
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.events = events;
function entity(services, config) {
const model = config.name;
return async (verb, correlationId, cmd) => {
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
if (cmd.dryRun === true) {
utils.log({
[config.correlation_field]: correlationId,
}, cmd.format);
return;
}
let res;
if (verb === 'data') {
res = await datastore.data(model, correlationId, cmd.onlyModels);
}
else {
/* @ts-ignore */
res = await datastore[verb](model, correlationId, true, cmd.onlyModels);
}
utils.log(res.data, cmd.format);
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.entity = entity;
function updateMany(services, config) {
const model = config.name;
return async (query, update, cmd) => {
try {
const datastore = services.datastores.get(config.datastore);
const _update = JSON.parse(update);
let stats;
await datastore?.updateOverwhelmingly(model, JSON.parse(query), async () => _update, (_stats) => {
stats = _stats;
Math.floor(stats.progress * 100) % 5 === 0 &&
utils.log(stats, cmd.format);
}, 100);
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.updateMany = updateMany;
function version(services, config) {
const model = config.name;
return async (correlationId, version, cmd) => {
try {
const datastore = services.datastores.get(config.datastore);
if (!datastore) {
return;
}
if (cmd.dryRun === true) {
utils.log({
[config.correlation_field]: correlationId,
}, cmd.format);
return;
}
let res;
const versionNumber = parseInt(version, 10);
const isVersionISOString = constants_1.REGEXP_DATE_ISO_STRING_8601.test(version);
if (isVersionISOString === true) {
res = await datastore.at(model, correlationId, version);
}
else {
res = await datastore.version(model, correlationId, versionNumber);
}
utils.log(res.data, cmd.format);
}
catch (err) {
if (err.response) {
utils.log(err.response.data, cmd.format);
return;
}
utils.log(err, cmd.format);
}
};
}
exports.version = version;
function register(services, config) {
const program = new commander_1.Command(config.name);
const summary = config.description?.split('\n', 2)[0] ?? '';
program
.summary(`${config.datastore}@${config.name} (v${config.version})${summary ? ' - ' + summary : ''}`)
.description(`datastore:\t${config.datastore}
version:\t${config.version}
created_at:\t${config.created_at}
updated_at:\t${config.updated_at}
description:
${(config.description || config.name || '').split('\n').shift()}
`);
let c = program
.command('schema')
.description(`Returns the JSON schema associated to ${config.name}`)
.action(() => {
utils.log(config.schema.model);
});
// Find
c = program
.command(`find`)
.description(`Get available entities for ${config.name}`);
addSchemaFields(c, config);
utils.addStandardFields(c);
c.addOption(new commander_1.Option('-s, --source <source>', 'Count source: entities or events')
.default('entities')
.choices(['entities', 'events']));
c.option('--fields <fields...>', 'Fields to return from the entity');
c.option('--sort <sorts...>', 'Sort entities on some specific fields');
c.option('--decrypt', 'Must try to decrypt entities', false);
c.option('--skip-hash', 'Remove the hash request', false);
c.option('--skip-validation', 'Skip query validation', false);
utils.addPaginationFields(c);
c.action(find(services, config));
// Get
c = program
.command(`get`)
.argument('<correlation_id>', 'Entity correlation ID')
.description(`Get one entity for ${config.name}`);
addSchemaFields(c, config);
utils.addStandardFields(c);
c.option('--decrypt', 'Must try to decrypt entities').action(get(services, config));
// Create
c = program
.command(`create`)
.description(`Create a new entity for ${config.name}`);
addSchemaFields(c, config);
utils.addStandardFields(c);
c.option('--skip-validation', 'Skip query validation', false);
c.action(create(services, config));
// Update
c = program
.command(`update`)
.argument('<correlation_id>', 'Entity correlation ID')
.description(`Update a single entity for ${config.name}`);
c.option('--upsert', 'Upsert the entity if not exists', false);
addSchemaFields(c, config);
utils.addStandardFields(c);
c.option('--skip-validation', 'Skip query validation', false);
c.action(update(services, config));
// UpdateMany
c = program
.command(`update:many`)
.argument('<query>', 'Matching query')
.argument('<update>', 'Update query')
.description(`Update all entities for ${config.name} matching the query`);
c.action(updateMany(services, config));
// Patch
c = program
.command(`patch`)
.argument('<correlation_id>', 'Entity correlation ID')
.argument('<patches...>', 'JSON Patch')
.description(`Patch a single entity for ${config.name}`);
utils.addStandardFields(c);
c.action(patch(services, config));
// Restore
c = program
.command(`restore`)
.argument('<correlation_id>', 'Entity correlation ID')
.argument('<version>', 'Entity version to restore')
.description(`Restore a single entity for ${config.name} to a given version`);
utils.addStandardFields(c);
c.action(restore(services, config));
// Count
c = program
.command(`count`)
.description(`Get count of entities for ${config.name} matching your query`);
addSchemaFields(c, config);
utils.addStandardFields(c);
c.addOption(new commander_1.Option('-s, --source <source>', 'Count source: entities or events')
.default('entities')
.choices(['entities', 'events']));
c.option('--skip-hash', 'Remove the hash request', true);
c.option('--skip-validation', 'Skip query validation', false);
c.action(count(services, config));
// Events
c = program
.command(`events`)
.argument('<correlation_id>', 'Entity correlation ID')
.description(`Get events associated to a ${config.name}`);
utils.addPaginationFields(c);
utils.addStandardFields(c);
c.option('--decrypt', 'Must try to decrypt entities').action(events(services, config));
// Entity
c = program
.command('entity')
.addArgument(new commander_1.Argument('<verb>', 'Verb to apply on the entity').choices([
'data',
'archive',
'unarchive',
'delete',
]))
.argument('<correlation_id>', 'Entity correlation ID')
.description(`Apply a specific method to data related to a single entity for ${config.name}`);
c.option('--only-models <models...>', 'Models to retrieve data from');
utils.addStandardFields(c);
c.action(entity(services, config));
// Version
c = program
.command('version')
.argument('<correlation_id>', 'Entity correlation ID')
.addArgument(new commander_1.Argument('<version>', 'Version'))
.description(`Get entity at version`);
utils.addStandardFields(c);
c.action(version(services, config));
return program;
}
exports.default = register;
//# sourceMappingURL=models.js.map