UNPKG

runtime-branding

Version:
157 lines 4.83 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const brand = Symbol(); const targetType = Symbol(); // TODO: do we need a global branding map? /** * Creates a new brand, identified by the provided branding object. * * @param brandObject An object that represents the brand. The shape of this object should be unique, and symbols may be used as unique keys. * @param callback An optional callback with variable arguments which is invoked during object branding */ function createBranding(brandObject, callback) { const brandedObjects = new WeakSet(); function has(obj) { return brandedObjects.has(obj); } function assert(obj) { if (!brandedObjects.has(obj)) { throw new Error("Object not branded."); } } function branding(obj) { if (has(obj)) { throw new Error("Object already branded."); } brandedObjects.add(obj); if (callback) { try { callback(obj, brandObject); } catch (e) { brandedObjects.delete(obj); throw e; } } return obj; } function refine(newBrand, newCallback) { const b = Object.assign({}, newBrand, brandObject); const refinedBranding = createBranding(b, newCallback); return merge(refinedBranding); } function merge(branding1) { return mixin(branding, branding1); } branding.has = has; // maybe all these should be built separately, then Object.assigned to branding() branding.assert = assert; branding.refine = refine; branding.merge = merge; branding[brand] = brandObject; return branding; } exports.createBranding = createBranding; /** * Mixes two different brandings in a single one. * * @param fn1 The first branding function * @param fn2 The second branding function */ function mixin(fn1, fn2) { function has(obj) { return fn1.has(obj) && fn2.has(obj); } function assert(obj) { fn1.assert(obj); fn2.assert(obj); } function branding(obj) { fn1(obj); fn2(obj); return obj; } function refine(newBrand, callback) { const b = Object.assign({}, newBrand, fn1[brand], fn2[brand]); return createBranding(b, callback); } function merge(branding1) { return mixin(branding, branding1); } const newBrand = Object.assign({}, fn1[brand], fn2[brand]); branding.has = has; branding.assert = assert; branding.refine = refine; branding.merge = merge; branding[brand] = newBrand; return branding; } const typeSym = Symbol(); function type(typeId, ...base) { return buildTypeBranding(typeId, ...base); } exports.type = type; function buildTypeBranding(typeId, ...base) { const brandObj = typeBrand(typeId, ...base); const branding = createBranding(brandObj, (o) => typeCallback(o, typeBranding)); const typeBranding = Object.assign(branding, brandObj); const has = typeBranding.has; typeBranding.has = function (obj) { const proto = Object.getPrototypeOf(obj); return has(obj) || (!!proto && has(proto)); }; const assert = typeBranding.assert; typeBranding.assert = function (obj) { if (!typeBranding.has(obj)) { assert(obj); } }; if (typeId instanceof Function) { typeBranding(typeId.prototype); } return typeBranding; } const objMap = new WeakMap(); function typeCallback(obj, brand) { if (!objMap.has(obj)) { // only for leaf objMap.set(obj, brand); brand.ancestors.forEach(a => a(obj)); } } function typeOf(obj) { if (obj instanceof Function) { obj = obj.prototype; } const res = objMap.get(obj) || objMap.get(Object.getPrototypeOf(obj)); if (!res) { throw new Error('Type ID not found for object'); } return res; } exports.typeOf = typeOf; function isTyped(obj) { return objMap.has(obj); } exports.isTyped = isTyped; function assertType(type, obj) { const assert = type.assert; assert(obj); } exports.assertType = assertType; function isTypeBranding(fn) { return fn[typeSym] === null; } exports.isTypeBranding = isTypeBranding; function typeBrand(typeId, ...base) { return { [typeSym]: null, typeId, base: new Set(base), ancestors: new Set(ancestorsOf(base)) }; } function ancestorsOf(base) { let ancestors = base; base.forEach(t => (ancestors = ancestors.concat(Array.from(t.ancestors)))); return new Set(ancestors); } //# sourceMappingURL=generic_branding.js.map