@platform/cell.schema
Version:
URI and database schemas for the `cell.os`.
155 lines (154 loc) • 5.33 kB
JavaScript
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(/\/*$/, '');
}