diff --git a/lib/kinds.js b/lib/kinds.js index c957f54..f896834 100644 --- a/lib/kinds.js +++ b/lib/kinds.js @@ -12,38 +12,62 @@ const SCOPE = ['application', 'global', 'local']; const STORE = ['persistent', 'memory']; const ALLOW = ['write', 'append', 'read']; +const KIND_STORED_DEFAULT_META = { + scope: 'application', + store: 'persistent', + allow: 'write', +}; + +const KIND_MEMORY_DEFAULT_META = { + scope: 'local', + store: 'memory', + allow: 'write', +}; + +const withDefaults = (meta, defaults, extra) => { + const result = { ...meta, ...extra }; + for (const [key, defaultValue] of Object.entries(defaults)) { + if (result[key] === undefined) result[key] = defaultValue; + } + return result; +}; + 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; - const parent = root.findReference(schema); + const { schema: schemaName, fields: fieldNames } = meta; + if (!schemaName) throw new Error('Invalid Projection: "schema" expected'); + if (!Array.isArray(fieldNames) || fieldNames.length === 0) + throw new Error('Invalid Projection: non-empty "fields" array expected'); + if (!root || typeof root.findReference !== 'function') + throw new Error('Invalid Projection: "root" should satisfy Schema API'); const defs = {}; - for (const key of fields) { - defs[key] = parent.fields[key]; + const referencedFields = root.findReference(schemaName)?.fields; + if (referencedFields) { + for (const name of fieldNames) defs[name] = referencedFields[name]; } + const metadata = withDefaults(meta, KIND_MEMORY_DEFAULT_META, { + kind, + parent: schemaName, + }); return { defs, metadata }; }; const kindStored = (kind, meta, root) => { - const { scope = 'application', store = 'persistent', allow = 'write' } = meta; - const metadata = { ...meta, kind, scope, store, allow }; + if (!root) throw new Error('"root" schema expected'); const id = root.name ? `${toLowerCamel(root.name)}Id` : 'id'; const defs = { [id]: '?string' }; + const metadata = withDefaults(meta, KIND_STORED_DEFAULT_META, { kind }); return { defs, metadata }; }; -const kindMemory = (kind, meta) => { - const { scope = 'local', store = 'memory', allow = 'write' } = meta; - return { metadata: { ...meta, kind, scope, store, allow }, defs: {} }; +const kindMemory = (kind, meta, root) => { + if (kind === 'projection') return projection(kind, meta, root); + const metadata = withDefaults(meta, KIND_MEMORY_DEFAULT_META, { kind }); + return { defs: {}, metadata }; }; 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); + const processKind = KIND_STORED.includes(kind) ? kindStored : kindMemory; + return processKind(kind, meta, root); }; module.exports = { diff --git a/test/kinds.js b/test/kinds.js new file mode 100644 index 0000000..b58d2e6 --- /dev/null +++ b/test/kinds.js @@ -0,0 +1,81 @@ +'use strict'; + +const metatests = require('metatests'); +const { getKindMetadata } = require('../lib/kinds.js'); + +metatests.test('Kinds: Projection', (test) => { + const getProjectionMeta = getKindMetadata.bind(null, 'projection'); + { + const meta = { + schema: 'Account', + fields: ['login', 'password'], + etc: ['another', 'field'], + }; + test.throws( + () => getProjectionMeta({}, null), + new Error('Invalid Projection: "schema" expected'), + ); + test.throws( + () => getProjectionMeta({ schema: 'Account' }, null), + new Error('Invalid Projection: non-empty "fields" array expected'), + ); + test.throws( + () => getProjectionMeta(meta, null), + new Error('Invalid Projection: "root" should satisfy Schema API'), + ); + test.doesNotThrow(() => + getProjectionMeta(meta, { findReference: new Function() }), + ); + } + + { + const meta = { + schema: 'Account', + fields: ['login', 'password'], + etc: ['another', 'field'], + }; + const { defs, metadata } = getProjectionMeta(meta, { + findReference: new Function(), + }); + test.strictEqual(metadata.kind, 'projection'); + test.strictEqual(metadata.scope, 'local'); + test.strictEqual(metadata.store, 'memory'); + test.strictEqual(metadata.allow, 'write'); + test.strictEqual(metadata.parent, 'Account'); + test.strictEqual(metadata.fields, ['login', 'password']); + test.strictEqual(Object.keys(defs).length, 0); + } + + { + const meta = { + scope: 'system', + store: 'persistent', + allow: 'append', + schema: 'Account', + fields: ['login', 'password', 'otp'], + }; + const root = { + findReference: (schemaName) => ({ + name: schemaName, + fields: { + login: 'any', + password: 'string', + otpNotMentioned: 'here', + }, + }), + }; + const { defs, metadata } = getProjectionMeta(meta, root); + test.strictEqual(metadata.kind, 'projection'); + test.strictEqual(metadata.scope, 'system'); + test.strictEqual(metadata.store, 'persistent'); + test.strictEqual(metadata.allow, 'append'); + test.strictEqual(metadata.parent, 'Account'); + test.strictEqual(metadata.fields, ['login', 'password', 'otp']); + test.strictEqual(Object.keys(defs).length, 3); + test.strictEqual(defs.login, 'any'); + test.strictEqual(defs.password, 'string'); + test.strictEqual(defs.otp, undefined); + } + + test.end(); +});