Skip to content

Commit

Permalink
Rewrite for code quality and flexibility
Browse files Browse the repository at this point in the history
Co-authored-by: Timur Shemsedinov <[email protected]>
PR-URL: #407
  • Loading branch information
georgolden and tshemsedinov committed Jun 22, 2022
1 parent 581d913 commit 6c0b3be
Show file tree
Hide file tree
Showing 27 changed files with 758 additions and 698 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"node": true
},
"parserOptions": {
"ecmaVersion": 2020
"ecmaVersion": "latest"
},
"rules": {
"arrow-parens": ["error", "always"],
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## [Unreleased][unreleased]

- Struct implementation
- Refactoring and code quality improvement
- Metadata handled separetely
- Error handling improved now errors are chained
- Custom Kinds support implemented with custom metadata `{ CustomKind: { customMetadata: 'meta' } }`
- Update dependencies: "eslint", "eslint-config-metarhia"
- Custom types syntax changed. No more hardcoded `pg`. Now you must use `metadata` which is static property of Type constructor.

## [2.0.0-alpha.2][] - 2022-05-29

- Nested schema with relations bugfix
Expand Down
71 changes: 33 additions & 38 deletions lib/kinds.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
'use strict';

const { toLowerCamel } = require('metautil');

const KIND_ENT = ['entity', 'registry', 'dictionary'];
const KIND_AUX = ['journal', 'details', 'relation', 'view'];
const KIND_STORED = [...KIND_ENT, ...KIND_AUX];
const KIND_MEMORY = ['struct', 'scalar', 'form', 'projection'];
const KIND = [...KIND_MEMORY, ...KIND_STORED];

const SCOPE = ['application', 'global', 'local'];
const STORE = ['persistent', 'memory'];
const ALLOW = ['write', 'append', 'read'];

const projection = (meta = {}, root) => {
const metadata = {
scope: meta.scope || 'local',
store: meta.store || 'memory',
allow: meta.allow || 'write',
};
const projection = (kind, meta, root) => {
const { scope = 'local', store = 'memory', allow = 'write' } = meta;
const metadata = { ...meta, kind, scope, store, allow };
const { schema, fields } = meta;
if (!schema && !fields) throw new Error('Invalid Projection');
metadata.parent = schema;
Expand All @@ -26,37 +26,32 @@ const projection = (meta = {}, root) => {
return { defs, metadata };
};

const kindStored = (meta = {}) => ({
metadata: {
scope: meta.scope || 'application',
store: meta.store || 'persistent',
allow: meta.allow || 'write',
},
});

const kindMemory = (meta = {}) => ({
metadata: {
scope: meta.scope || 'local',
store: meta.store || 'memory',
allow: meta.allow || 'write',
},
});

const KIND = {
registry: kindStored,
entity: kindStored,
dictionary: kindStored,
relation: kindStored,
journal: kindStored,
details: kindStored,
view: kindStored,

struct: kindMemory,
scalar: kindMemory,
form: kindMemory,
projection,

_any: kindMemory,
const kindStored = (kind, meta, root) => {
const { scope = 'application', store = 'persistent', allow = 'write' } = meta;
const metadata = { ...meta, kind, scope, store, allow };
const id = root.name ? `${toLowerCamel(root.name)}Id` : 'id';
const defs = { [id]: '?string' };
return { defs, metadata };
};

const kindMemory = (kind, meta) => {
const { scope = 'local', store = 'memory', allow = 'write' } = meta;
return { metadata: { ...meta, kind, scope, store, allow }, defs: {} };
};

module.exports = { KIND, KIND_STORED, KIND_MEMORY, SCOPE, STORE, ALLOW };
const getKindMetadata = (kind, meta = {}, root) => {
if (kind === 'projection') return projection(kind, meta, root);
if (KIND_MEMORY.includes(kind)) return kindMemory(kind, meta);
if (KIND_STORED.includes(kind)) return kindStored(kind, meta, root);
return kindMemory(kind, meta);
};

module.exports = {
getKindMetadata,
KIND,
KIND_STORED,
KIND_MEMORY,
SCOPE,
STORE,
ALLOW,
};
126 changes: 126 additions & 0 deletions lib/metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
'use strict';

const { getKindMetadata } = require('./kinds.js');

const errPrefix = 'Field';
const OPTIONS = ['validate', 'parse', 'serialize', 'format'];
const metadataCollections = ['indexes', 'options'];

class SchemaError {
#path;

constructor(path) {
this.#path = path;
this.errors = [];
this.valid = true;
}

add(err) {
if (SchemaError.isInstance(err)) {
this.errors.push(...err.errors);
} else {
const errs = SchemaError.format(err, this.#path);
if (errs) this.errors.push(...errs);
}
this.valid = this.errors.length === 0;
return this;
}

static format(err, path = '') {
const pfx = `${errPrefix} "${path}" `;
if (typeof err === 'boolean') {
return err ? null : [`${pfx}validation error`];
}
if (err) {
const unprefixed = Array.isArray(err) ? err : [err];
return unprefixed.map((e) => (e.startsWith(errPrefix) ? e : pfx + e));
}
return null;
}

static isInstance(err) {
return err && err.errors && Array.isArray(err.errors);
}
}

class Options {
constructor() {
this.validate = null;
this.format = null;
this.parse = null;
this.serialize = null;
}

extract(key, field) {
const isOption = OPTIONS.includes(key) && typeof field === 'function';
if (isOption) this[key] = field;
return isOption;
}
}

class Indexes {
extract(key, field) {
const { index, primary, unique, many } = field;
const isIndex = Array.isArray(index || primary || unique);
if (isIndex || many) this[key] = field;
return isIndex;
}
}

class SchemaMetadata {
constructor() {
this.kind = 'struct';
this.scope = 'local';
this.store = 'memory';
this.allow = 'write';
this.parent = '';
this.indexes = new Indexes();
this.options = new Options();
this.custom = {};
this.references = new Set();
this.relations = new Set();
}

#setMany(values) {
const { kind, scope, store, allow, parent, ...custom } = values;
this.kind = kind || this.kind;
this.scope = scope || this.scope;
this.store = store || this.store;
this.allow = allow || this.allow;
this.parent = parent || this.parent;
this.custom = custom;
}

updateFromSchema({ references = [], relations = [] }) {
references.forEach(this.references.add, this.references);
relations.forEach(this.relations.add, this.relations);
}

updateFromKind({ kind, meta, root }) {
const { defs, metadata } = getKindMetadata(kind, meta, root);
this.#setMany(metadata);
return defs;
}

extractMetadata(defs) {
for (const [key, field] of Object.entries(defs)) {
for (const collection of metadataCollections) {
const extracted = this[collection].extract(key, field);
if (extracted) Reflect.deleteProperty(defs, key);
}
}
return defs;
}

validate(value, path) {
if (!this.options.validate) return;
const error = new SchemaError(path);
try {
return error.add(this.options.validate(value, path));
} catch (err) {
return error.add(`validation failed ${err}`);
}
}
}

module.exports = { SchemaMetadata, SchemaError };
4 changes: 2 additions & 2 deletions lib/model.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict';

const { Schema } = require('./schema.js');
const { prepareTypes } = require('./types.js');
const { typeFactory } = require('./types.js');
const { firstKey } = require('./util.js');

class Model {
constructor(types, entities, database = null) {
this.types = prepareTypes(types);
this.types = typeFactory(types);
this.entities = new Map();
this.database = database;
this.order = new Set();
Expand Down
Loading

0 comments on commit 6c0b3be

Please sign in to comment.