UNPKG

proxy-extend

Version:

Transparently extend a JS object with additional properties (using ES6 Proxy)

1 lines 14.8 kB
{"version":3,"file":"proxyExtend.mjs","names":["$msg","hasOwnProperty","obj","propKey","Object","prototype","call","nullObject","create","TypedArray","getPrototypeOf","Int8Array","nodeInspectCustom","Symbol","for","proxyKey","extend","_value","_extension","arguments","length","undefined","value","extension","TypeError","unproxied","isString","isNumber","valueType","target","String","Number","usesInternalSlots","Boolean","Date","RegExp","Map","WeakMap","Set","WeakSet","ArrayBuffer","handler","has","Reflect","get","receiver","targetProp","toString","bind","valueOf","Proxy","isProxyable","isProxy","unwrapProxy","proxy","is","unwrap","registerProxyFormatter","require","util","inspect","replDefaults","showProxy","window","Array","isArray","devtoolsFormatters","push","header","object"],"sources":["../../src/proxyExtend.js"],"sourcesContent":["\nimport $msg from 'message-tag';\n\n\n// Version of `hasOwnProperty` that doesn't conflict\nconst hasOwnProperty = (obj, propKey) => Object.prototype.hasOwnProperty.call(obj, propKey);\n\n// Cache some values\nconst nullObject = Object.create(null);\nconst TypedArray = Object.getPrototypeOf(Int8Array);\nconst nodeInspectCustom = Symbol.for('nodejs.util.inspect.custom');\n\n\nexport const proxyKey = Symbol('proxy-wrapper.proxy');\n\nexport const extend = (_value, _extension = nullObject) => {\n let value = _value;\n let extension = _extension;\n \n if (typeof extension !== 'object' || extension === null) {\n throw new TypeError($msg`Extension must be an object, given ${extension}`);\n }\n \n // Check if the given value is already a proxy with extension. If so, flatten.\n if (typeof value === 'object' && value !== null && proxyKey in value) {\n const unproxied = value[proxyKey];\n value = unproxied.value;\n extension = { ...unproxied.extension, ...extension };\n }\n \n let isString = false;\n let isNumber = false;\n \n // Handle primitive values. Because a Proxy always behaves as an object, we cannot really transparently\n // \"simulate\" a primitive. However, we use sensible equivalents where possible.\n const valueType = typeof value;\n let target = value;\n if (valueType === 'undefined') {\n throw new TypeError($msg`Cannot construct proxy, given \\`undefined\\``);\n } else if (value === null) {\n target = nullObject;\n } else if (valueType === 'string') {\n target = new String(value);\n isString = true;\n } else if (valueType === 'number') {\n target = new Number(value);\n isNumber = true;\n } else if (valueType === 'bigint') {\n // TODO: we could use a boxed `BigInt` through `Object()` (e.g. `Object(42n) instanceof BigInt`):\n // https://2ality.com/2022/02/wrapper-objects.html\n throw new TypeError($msg`Cannot construct proxy from bigint, given ${value}`);\n } else if (valueType === 'boolean') {\n // Note: we could use a boxed `Boolean`, but it would not be very useful because there's not much you can\n // do with it. Boxed booleans (including `new Boolean(false)`) are treated as truthy in logic operations.\n throw new TypeError($msg`Cannot construct proxy from boolean, given ${value}`);\n } else if (valueType === 'symbol') {\n throw new TypeError($msg`Cannot construct proxy from symbol, given ${value}`);\n } else if (valueType !== 'object' && valueType !== 'function') {\n // Note: this shouldn't happen, unless there's a new type of primitive added to JS\n throw new TypeError($msg`Cannot construct proxy, given value of unknown type ${valueType}`);\n }\n \n // Some methods of built-in types cannot be proxied, i.e. they need to bound directly to the\n // target. Because they explicitly check the type of `this` (e.g. `Date`), or because they need\n // to access an internal slot of the target (e.g. `String.toString`).\n // https://stackoverflow.com/questions/36394479/proxies-on-regexps-and-boxed-primitives\n // https://stackoverflow.com/questions/47874488/proxy-on-a-date-object\n // https://stackoverflow.com/questions/43927933/why-is-set-incompatible-with-proxy\n const usesInternalSlots =\n target instanceof String\n || target instanceof Number\n || target instanceof Boolean\n || target instanceof Date\n || target instanceof RegExp\n || target instanceof Map\n || target instanceof WeakMap\n || target instanceof Set\n || target instanceof WeakSet\n || target instanceof ArrayBuffer\n || target instanceof TypedArray;\n \n \n const handler = {\n has(target, propKey) {\n if (hasOwnProperty(extension, propKey)) {\n // Note: use `hasOwnProperty` for the extension, rather than `in`, because we do not want to\n // consider properties in the prototype chain as being part of the extension.\n return true;\n }\n \n // Implement `toJSON` for boxed primitives (otherwise `JSON.stringify` will not work properly).\n if (propKey === 'toJSON' && (isString || isNumber)) { return true; }\n \n if (propKey === nodeInspectCustom) { return true; }\n if (propKey === proxyKey) { return true; }\n \n return Reflect.has(target, propKey);\n },\n \n get(target, propKey, receiver) {\n // Backdoor to get the internal proxied data (value and extension).\n // Note: use `value` here, not `target` (target is just an internal representation).\n if (propKey === proxyKey) { return { value, extension }; }\n \n let targetProp = undefined;\n if (hasOwnProperty(extension, propKey)) {\n targetProp = extension[propKey];\n } else if (propKey in target) {\n targetProp = target[propKey];\n \n // Note: any getter properties will receive the `target`, rather than the proxy as their `this`\n // value. Thus, getters will not have access to the extension. If you really need this behavior,\n // you can use the following. But it's not recommended, due to the impact on performance.\n /*\n if (hasOwnProperty(target, propKey)) {\n const descriptor = Object.getOwnPropertyDescriptor(target, propKey);\n if (typeof descriptor.get === 'function') {\n targetProp = descriptor.get.call(receiver);\n }\n }\n */\n } else {\n // Fallback: property is present in neither the target nor the extension\n \n // Implement `toJSON` for boxed primitives (otherwise `JSON.stringify` will not work properly).\n if (propKey === 'toJSON') {\n if (isString) {\n targetProp = target.toString.bind(target);\n } else if (isNumber) {\n targetProp = target.valueOf.bind(target);\n }\n }\n \n if (propKey === nodeInspectCustom) { targetProp = () => target; }\n }\n \n if (typeof targetProp === 'function') {\n if (usesInternalSlots) {\n // Have `this` bound to the original target\n return targetProp.bind(target);\n } else {\n // Unbound (i.e. `this` can be bound to anything, usually will be the proxy object)\n return targetProp;\n }\n } else {\n return targetProp;\n }\n },\n };\n \n return new Proxy(target, handler);\n};\n\n// Whether the given value can be proxied\nexport const isProxyable = value => {\n if (typeof value === 'object') { // Also covers the case where `value === null`\n return true;\n } else if (typeof value === 'function') {\n return true;\n } else if (typeof value === 'string') {\n return true;\n } else if (typeof value === 'number') {\n return true;\n } else {\n return false;\n }\n};\n\n// Whether the given value is a proxy\nexport const isProxy = value => {\n return typeof value === 'object' && value !== null && proxyKey in value;\n};\n\n// Unwrap the given proxy to access its internal data\nexport const unwrapProxy = proxy => {\n if (!isProxy(proxy)) {\n throw new TypeError($msg`Cannot unwrap input, expected a proxy, received: ${proxy}`);\n }\n \n return proxy[proxyKey];\n};\n\n// Add some properties to `extend` as shorthand\nextend.is = isProxy;\nextend.unwrap = unwrapProxy;\n\n// Make formatting of proxies a little nicer\nexport const registerProxyFormatter = () => {\n if (typeof require === 'function') {\n const util = require('util');\n \n if (util.inspect && util.inspect.replDefaults) {\n util.inspect.replDefaults.showProxy = false;\n }\n }\n \n // https://stackoverflow.com/questions/55733647/chrome-devtools-formatter-for-javascript-proxy\n if (typeof window === 'object' && window !== null) {\n if (!Array.isArray(window.devtoolsFormatters)) {\n window.devtoolsFormatters = [];\n }\n \n window.devtoolsFormatters.push({\n header(value) {\n if (!isProxy(value)) {\n return null;\n }\n \n return ['object', { object: value[proxyKey].value }];\n },\n });\n }\n};\n\nexport default extend;\n"],"mappings":"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"}