UNPKG

interface-datastore

Version:
399 lines 10.5 kB
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'; import { toString as uint8ArrayToString } from 'uint8arrays/to-string'; const pathSepS = '/'; const pathSepB = new TextEncoder().encode(pathSepS); const pathSep = pathSepB[0]; /** * A Key represents the unique identifier of an object. * Our Key scheme is inspired by file systems and Google App Engine key model. * Keys are meant to be unique across a system. Keys are hierarchical, * incorporating more and more specific namespaces. Thus keys can be deemed * 'children' or 'ancestors' of other keys: * - `new Key('/Comedy')` * - `new Key('/Comedy/MontyPython')` * Also, every namespace can be parametrized to embed relevant object * information. For example, the Key `name` (most specific namespace) could * include the object type: * - `new Key('/Comedy/MontyPython/Actor:JohnCleese')` * - `new Key('/Comedy/MontyPython/Sketch:CheeseShop')` * - `new Key('/Comedy/MontyPython/Sketch:CheeseShop/Character:Mousebender')` * */ export class Key { _buf; /** * @param {string | Uint8Array} s * @param {boolean} [clean] */ constructor(s, clean) { if (typeof s === 'string') { this._buf = uint8ArrayFromString(s); } else if (s instanceof Uint8Array) { this._buf = s; } else { throw new Error('Invalid key, should be String of Uint8Array'); } if (clean == null) { clean = true; } if (clean) { this.clean(); } if (this._buf.byteLength === 0 || this._buf[0] !== pathSep) { throw new Error('Invalid key'); } } /** * Convert to the string representation * * @param {import('uint8arrays/to-string').SupportedEncodings} [encoding] - The encoding to use. * @returns {string} */ toString(encoding = 'utf8') { return uint8ArrayToString(this._buf, encoding); } /** * Return the Uint8Array representation of the key * * @returns {Uint8Array} */ uint8Array() { return this._buf; } /** * Return string representation of the key * * @returns {string} */ get [Symbol.toStringTag]() { return `Key(${this.toString()})`; } /** * Constructs a key out of a namespace array. * * @param {Array<string>} list - The array of namespaces * @returns {Key} * * @example * ```js * Key.withNamespaces(['one', 'two']) * // => Key('/one/two') * ``` */ static withNamespaces(list) { return new Key(list.join(pathSepS)); } /** * Returns a randomly (uuid) generated key. * * @returns {Key} * * @example * ```js * Key.random() * // => Key('/344502982398') * ``` */ static random() { return new Key(Math.random().toString().substring(2)); } /** * @param {*} other */ static asKey(other) { if (other instanceof Uint8Array || typeof other === 'string') { // we can create a key from this return new Key(other); } if (typeof other.uint8Array === 'function') { // this is an older version or may have crossed the esm/cjs boundary return new Key(other.uint8Array()); } return null; } /** * Cleanup the current key * * @returns {void} */ clean() { if (this._buf == null || this._buf.byteLength === 0) { this._buf = pathSepB; } if (this._buf[0] !== pathSep) { const bytes = new Uint8Array(this._buf.byteLength + 1); bytes.fill(pathSep, 0, 1); bytes.set(this._buf, 1); this._buf = bytes; } // normalize does not remove trailing slashes while (this._buf.byteLength > 1 && this._buf[this._buf.byteLength - 1] === pathSep) { this._buf = this._buf.subarray(0, -1); } } /** * Check if the given key is sorted lower than ourself. * * @param {Key} key - The other Key to check against * @returns {boolean} */ less(key) { const list1 = this.list(); const list2 = key.list(); for (let i = 0; i < list1.length; i++) { if (list2.length < i + 1) { return false; } const c1 = list1[i]; const c2 = list2[i]; if (c1 < c2) { return true; } else if (c1 > c2) { return false; } } return list1.length < list2.length; } /** * Returns the key with all parts in reversed order. * * @returns {Key} * * @example * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').reverse() * // => Key('/Actor:JohnCleese/MontyPython/Comedy') * ``` */ reverse() { return Key.withNamespaces(this.list().slice().reverse()); } /** * Returns the `namespaces` making up this Key. * * @returns {Array<string>} */ namespaces() { return this.list(); } /** * Returns the "base" namespace of this key. * * @returns {string} * * @example * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').baseNamespace() * // => 'Actor:JohnCleese' * ``` */ baseNamespace() { const ns = this.namespaces(); return ns[ns.length - 1]; } /** * Returns the `list` representation of this key. * * @returns {Array<string>} * * @example * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').list() * // => ['Comedy', 'MontyPythong', 'Actor:JohnCleese'] * ``` */ list() { return this.toString().split(pathSepS).slice(1); } /** * Returns the "type" of this key (value of last namespace). * * @returns {string} * * @example * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').type() * // => 'Actor' * ``` */ type() { return namespaceType(this.baseNamespace()); } /** * Returns the "name" of this key (field of last namespace). * * @returns {string} * * @example * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').name() * // => 'JohnCleese' * ``` */ name() { return namespaceValue(this.baseNamespace()); } /** * Returns an "instance" of this type key (appends value to namespace). * * @param {string} s - The string to append. * @returns {Key} * * @example * ```js * new Key('/Comedy/MontyPython/Actor').instance('JohnClesse') * // => Key('/Comedy/MontyPython/Actor:JohnCleese') * ``` */ instance(s) { return new Key(this.toString() + ':' + s); } /** * Returns the "path" of this key (parent + type). * * @returns {Key} * * @example * ```js * new Key('/Comedy/MontyPython/Actor:JohnCleese').path() * // => Key('/Comedy/MontyPython/Actor') * ``` */ path() { let p = this.parent().toString(); if (!p.endsWith(pathSepS)) { p += pathSepS; } p += this.type(); return new Key(p); } /** * Returns the `parent` Key of this Key. * * @returns {Key} * * @example * ```js * new Key("/Comedy/MontyPython/Actor:JohnCleese").parent() * // => Key("/Comedy/MontyPython") * ``` */ parent() { const list = this.list(); if (list.length === 1) { return new Key(pathSepS); } return new Key(list.slice(0, -1).join(pathSepS)); } /** * Returns the `child` Key of this Key. * * @param {Key} key - The child Key to add * @returns {Key} * * @example * ```js * new Key('/Comedy/MontyPython').child(new Key('Actor:JohnCleese')) * // => Key('/Comedy/MontyPython/Actor:JohnCleese') * ``` */ child(key) { if (this.toString() === pathSepS) { return key; } else if (key.toString() === pathSepS) { return this; } return new Key(this.toString() + key.toString(), false); } /** * Returns whether this key is a prefix of `other` * * @param {Key} other - The other key to test against * @returns {boolean} * * @example * ```js * new Key('/Comedy').isAncestorOf('/Comedy/MontyPython') * // => true * ``` */ isAncestorOf(other) { if (other.toString() === this.toString()) { return false; } return other.toString().startsWith(this.toString()); } /** * Returns whether this key is a contains another as prefix. * * @param {Key} other - The other Key to test against * @returns {boolean} * * @example * ```js * new Key('/Comedy/MontyPython').isDecendantOf('/Comedy') * // => true * ``` */ isDecendantOf(other) { if (other.toString() === this.toString()) { return false; } return this.toString().startsWith(other.toString()); } /** * Checks if this key has only one namespace. * * @returns {boolean} */ isTopLevel() { return this.list().length === 1; } /** * Concats one or more Keys into one new Key. * * @param {Array<Key>} keys - The array of keys to concatenate * @returns {Key} */ concat(...keys) { return Key.withNamespaces([...this.namespaces(), ...flatten(keys.map(key => key.namespaces()))]); } } /** * The first component of a namespace. `foo` in `foo:bar` * * @param {string} ns * @returns {string} */ function namespaceType(ns) { const parts = ns.split(':'); if (parts.length < 2) { return ''; } return parts.slice(0, -1).join(':'); } /** * The last component of a namespace, `baz` in `foo:bar:baz`. * * @param {string} ns * @returns {string} */ function namespaceValue(ns) { const parts = ns.split(':'); return parts[parts.length - 1]; } /** * Flatten array of arrays (only one level) * * @template T * @param {Array<any>} arr * @returns {T[]} */ function flatten(arr) { return ([]).concat(...arr); } //# sourceMappingURL=key.js.map