UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

202 lines (163 loc) 4.5 kB
import type { JSONSchemaType } from 'ajv'; import type { AnyObject } from '../typings'; import assert from 'node:assert'; import crypto from 'node:crypto'; import util from 'node:util'; import _ from 'lodash'; import Ajv from 'ajv'; export function unique(value: any, index: any, self: any) { return self.indexOf(value) === index; } export function random(): number { return crypto.getRandomValues(new Uint8Array(1))[0] / 256; } export function getDate(str?: string | number | Date) { return !!str || str === 0 ? new Date(str) : new Date(); } export function getDateNow() { return Date.now(); } export function mergeWithReplacedArrays( _srcValue: AnyObject, objValue: AnyObject, ) { return Array.isArray(objValue) ? objValue : undefined; } export function mapValuesDeep( obj: AnyObject, handler: (obj: AnyObject, key: string) => AnyObject = (v) => v, ) { Object.keys(obj).forEach((key) => { obj[key] = handler(obj[key], key); if (typeof obj[key] === 'object' && obj[key] !== null) { obj[key] = mapValuesDeep(obj[key], handler); } }); return obj; } export function deepCoerceProp( obj: AnyObject, schema: JSONSchemaType<AnyObject>, fn: (v: AnyObject, s: JSONSchemaType<AnyObject>, p: string[]) => AnyObject, path: string[], name: string, ) { const prop = obj[name]; const _path = [...path, name]; const _schema = _.get(schema.properties, name, schema); const { type, format } = _schema; if (type === 'array') { obj[name] = deepCoerce(prop, _schema.items || {}, fn, _path); return; } if (typeof prop === 'object' && prop !== null) { obj[name] = deepCoerce(prop, _schema, fn, _path); return; } if (type === 'integer') { const _value = Number.parseInt(obj[name], 10); if (isNaN(_value) === false) { obj[name] = _value; } return; } if (type === 'number') { const _value = parseFloat(obj[name]); if (isNaN(_value) === false) { obj[name] = _value; } return; } if (type === 'boolean') { obj[name] = Boolean(JSON.parse(obj[name])); return; } if ( (format === 'date' || format === 'date-time') && (typeof obj[name] === 'string' || typeof obj[name] === 'number') ) { obj[name] = new Date(obj[name]); return; } obj[name] = fn(prop, _schema, _path); } /** * Deep coerce object against its JSON Schema * * @param {object} obj The object to freeze * @returns {object} The freezed object */ export function deepCoerce( obj: AnyObject, schema: JSONSchemaType<AnyObject>, fn: (v: AnyObject, s: JSONSchemaType<AnyObject>, p: string[]) => AnyObject = ( v, ) => v, path: string[] = [], ) { if (Object.isFrozen(obj)) { return obj; } let propNames = Object.getOwnPropertyNames(obj); if (Array.isArray(obj)) { propNames = propNames.filter((p) => p !== 'length'); } for (const name of propNames) { deepCoerceProp(obj, schema, fn, path, name); } return obj; } export function processExplanationPlan(raw: any) { const winningPlan = _.get(raw, 'queryPlanner.winningPlan'); const executionStats = _.get(raw, 'executionStats'); return { stage: winningPlan?.inputStage?.stage ?? winningPlan?.stage, executation_time_ms: executionStats?.executionTimeMillis, index_name: winningPlan?.inputStage?.indexName, keys_examined: executionStats?.totalKeysExamined, docs_examined: executionStats?.totalDocsExamined, winning_plan: winningPlan, raw, }; } export function validateEntity( validator: Ajv, entity: AnyObject, schema: AnyObject, throwOnInvalidEvent: boolean = true, ): boolean { const schemaId = `entity`; let validate = validator.getSchema(schemaId); if (!validate) { validator.addSchema(schema, schemaId); validate = validator.getSchema(schemaId); } const isValid = validate!(entity) as boolean; if (isValid === false && throwOnInvalidEvent === true) { const assertionErrorPayload = { message: 'State schema validation error', expected: null, actual: util.inspect( { schema, entity, errors: validate!.errors, }, false, null, false, ), }; const err = new assert.AssertionError(assertionErrorPayload); // @ts-ignore err.details = validate!.errors; // @ts-ignore err.details.push({ entity, }); // @ts-ignore err.entity = entity; throw err; } return isValid; }