UNPKG

@platform/cell.schema

Version:

URI and database schemas for the `cell.os`.

155 lines (154 loc) 5.33 kB
import { queryString, wildcard } from '../common'; import { Uri } from '../Uri'; export class Links { constructor(args) { this.prefix = args.prefix; } isKey(input) { return Links.isKey(this.prefix, input); } static isKey(prefix, input) { input = (input || '').toString().trim(); return input.startsWith(prefix); } total(links = {}) { return Links.total(this.prefix, links); } static total(prefix, links = {}) { return Object.keys(links).reduce((acc, next) => { return Links.isKey(prefix, next) ? acc + 1 : acc; }, 0); } toKey(input) { return Links.toKey(this.prefix, input); } static toKey(prefix, input) { input = (input || '').trim(); if (!input) { throw new Error(`Link key must have a value.`); } prefix = (prefix || '').trim().replace(/:*$/, ''); return `${prefix}:${encode(input)}`; } toList(links = {}) { return Links.toList(this.prefix, links); } static toList(prefix, links = {}) { return Object.keys(links) .map(key => ({ key, value: links[key] })) .filter(({ key }) => Links.isKey(prefix, key)) .map(({ key, value }) => ({ key, value })); } parseKey(linkKey) { return Links.parseKey(this.prefix, linkKey); } static parseKey(prefix, linkKey) { const key = (linkKey || '').trim(); let path = key.replace(new RegExp(`^${prefix}\:`), ''); path = shouldDecode(path) ? Links.decodeKey(path) : path; const lastSlash = path.lastIndexOf('/'); const lastPeriod = path.lastIndexOf('.'); const name = lastSlash < 0 ? path : path.substring(lastSlash + 1); const dir = lastSlash < 0 ? '' : path.substring(0, lastSlash); const ext = lastPeriod < 0 ? '' : path.substring(lastPeriod + 1); const res = { prefix, key, path, name, dir, ext }; return res; } parseValue(linkValue) { return Links.parseValue(linkValue); } static parseValue(linkValue) { const parts = (linkValue || '') .trim() .split('?') .map(part => part.trim()); const uri = Uri.parse(parts[0] || '').parts; const query = queryString.toObject(parts[1]); const value = parts.join('?').replace(/\?$/, ''); const res = { value, uri, query }; return res; } parse(linkKey, linkValue) { return Links.parseLink(this.prefix, linkKey, linkValue); } static parseLink(prefix, linkKey, linkValue) { const key = Links.parseKey(prefix, linkKey); const value = Links.parseValue(linkValue); return Object.assign(Object.assign({}, key), value); } find(links = {}) { return Links.find(this.prefix, links); } static find(prefix, links = {}) { return { byName(path) { path = (path || '').trim().replace(/^\/*/, ''); return Object.keys(links) .map(key => ({ key, value: links[key] })) .filter(({ key }) => Links.isKey(prefix, key)) .find(({ key }) => { const parsed = Links.parseKey(prefix, key); return (path || '').includes('*') ? wildcard.isMatch(parsed.path, path) : parsed.path === path; }); }, }; } } Links.encodeKey = encode; Links.decodeKey = decode; Links.create = (prefix) => new Links({ prefix }); function encode(input) { const ILLEGAL = [':']; ILLEGAL.forEach(char => { if (input.includes(char)) { throw new Error(`Link key cannot contain "${char}" character.`); } }); input = trimSlashes(input); const escapeMultiPeriods = (input) => { const regex = new RegExp(/\.{2,}/g); const match = regex.exec(input); if (match && match[0]) { const left = input.substring(0, match.index); const middle = ':'.repeat(match[0].length); const right = input.substring(match.index + match[0].length); input = `${left}[${middle}]${right}`; return escapeMultiPeriods(input); } else { return input; } }; input = escapeMultiPeriods(input) .replace(/\//g, '::') .replace(/\./g, ':'); return input; } function decode(input) { const unescapeMultiPeriods = (input) => { const regex = new RegExp(/\[:{2,}\]/g); const match = regex.exec(input); if (match && match[0]) { const left = input.substring(0, match.index); const middle = '.'.repeat(match[0].length - 2); const right = input.substring(match.index + match[0].length); input = `${left}${middle}${right}`; return unescapeMultiPeriods(input); } else { return input; } }; input = unescapeMultiPeriods(input) .replace(/::/g, '/') .replace(/:/g, '.'); return input; } function shouldDecode(input) { return input.includes(':'); } function trimSlashes(input) { return (input || '').replace(/^\/*/, '').replace(/\/*$/, ''); }