{"id":20514120,"url":"https://github.com/webreflection/js-proxy","last_synced_at":"2025-04-14T00:05:42.094Z","repository":{"id":228364945,"uuid":"773522774","full_name":"WebReflection/js-proxy","owner":"WebReflection","description":"The one-stop shop solution for JS Proxies and FFI APIs.","archived":false,"fork":false,"pushed_at":"2025-02-27T16:28:08.000Z","size":151,"stargazers_count":26,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-14T00:05:32.012Z","etag":null,"topics":["destructor","ffi","garbage-collection","proxy"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WebReflection.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-17T22:02:55.000Z","updated_at":"2025-02-27T16:28:10.000Z","dependencies_parsed_at":"2024-03-25T12:53:49.925Z","dependency_job_id":"14e931dd-eb50-409b-8246-12d41664d019","html_url":"https://github.com/WebReflection/js-proxy","commit_stats":null,"previous_names":["webreflection/js-proxy"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fjs-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fjs-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fjs-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fjs-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WebReflection","download_url":"https://codeload.github.com/WebReflection/js-proxy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248799954,"owners_count":21163404,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["destructor","ffi","garbage-collection","proxy"],"created_at":"2024-11-15T21:14:52.098Z","updated_at":"2025-04-14T00:05:42.060Z","avatar_url":"https://github.com/WebReflection.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![build status](https://github.com/WebReflection/js-proxy/actions/workflows/node.js.yml/badge.svg)](https://github.com/WebReflection/js-proxy/actions) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/js-proxy/badge.svg?branch=main)](https://coveralls.io/github/WebReflection/js-proxy?branch=main)\n\n\u003csup\u003e**Social Media Photo by [Vinu T](https://unsplash.com/@happy_pixel?utm_content=creditCopyText\u0026utm_medium=referral\u0026utm_source=unsplash) on [Unsplash](https://unsplash.com/photos/a-small-waterfall-in-the-middle-of-a-forest-DHo1nNUI0y4?utm_content=creditCopyText\u0026utm_medium=referral\u0026utm_source=unsplash)**\u003c/sup\u003e\n\nThe \"*one-stop shop*\" solution for JS Proxies and FFI APIs.\n\n**[Documentation](https://webreflection.github.io/js-proxy/)**\n\n- - -\n\n### Table of content\n\n  * **[API](#api)** that describes the default exported utility\n  * **[jsProxy](#jsproxy)** that describes the namespace returned by the utility\n  * **[MITM](#mitm)** that describes what `js-proxy/mitm` exports as extra utility\n  * **[Heap](#heap)** that describes what `js-proxy/heap` exports as extra utility\n  * **[Traps](#traps)** that describes what `js-proxy/traps` exports\n  * **[Types](#types)** that describes what `js-proxy/types` exports\n\n## API\n\n### `define(namespace):jsProxy`\n\nThe default export provides an utility to define various handlers for any kind of proxied value and returns a [jsProxy](#jsproxy) object literal.\n\nEach handler can have zero, one or more [proxy traps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy#handler_functions) *plus* the following extra handlers:\n\n  * **destruct** which, if present, will orchestrate automatically a [FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry) logic to invoke such trap once its proxied value is not used anymore in the wild.\n  * **valueOf** which, if present, allows the `valueOf(proxy)` utility to retrieve directly the underlying proxied value.\n\n\u003e [!Important]\n\u003e \n\u003e If the namespace contains `object`, `array`, or `function` as own entries the value can be either a reference to those types or actually a number or any other primitive which goal is to reflect proxies across worlds (boundaries, workers, realms, interpreters).\n\u003e \n\u003e In case those references are primitives it is mandatory to define all native traps otherwise the `Reflect` methods would fail at dealing with numbers, strings, or any other kind of primitive.\n\u003e \n\u003e Any other name will simply directly accept references, but not primitives, still providing the special methods that are indeed available to every type of proxy.\n\n#### Example\n\n```js\nimport define from 'js-proxy';\n\n// simply returns whatever value is received\nconst identity = value =\u003e value;\n\n// te jsProxy is an object with these fields / utilities:\nconst { proxy, release, typeOf, valueOf } = define({\n  object: { valueOf: identity },\n  array: { valueOf: identity },\n  function: { valueOf: identity },\n  direct: {\n    destruct(ref) {\n      console.log('this reference is no longer needed', ref);\n    }\n  },\n});\n\n// object, array and function always act\n// like proxies and values for objects, arrays or functions\n// any other namespace entry uses directly the referenced value.\nconst object = proxy.object([]);  // still an object\nconst array = proxy.array({});    // still an array\nconst fn = proxy.function(123);   // still a function\n\nlet any = proxy.direct({});\n\n// all true\ntypeOf(object) === \"object\" \u0026\u0026 !Array.isArray(object);\ntypeOf(array) === \"array\" \u0026\u0026 Array.isArray(array);\ntypeOf(fn) === \"function\" \u0026\u0026 typeof fn === \"function\";\ntypeOf(any) === \"direct\"; // \u003c-- !!!\n\n// retrieve the original value\nvalueOf(object).length; // 0\nvalueOf(fn) === 123;    // true\n\n// no valueOf trap defined:\nvalueOf(any) === any;   // true\n\nany = null;\n// will eventually log:\n// \"this reference is no longer needed\", {}\n```\n\nThe reason for `object`, `array`, and `function` to have a special treatment is the fact both `typeof` and `Array.isArray` can actually drill into the proxied type so that this module guarantees that if you meant to proxy an *array* or a *function*, these will reflect their entity across introspection related operations, also providing a way to simply proxy memory addresses or any other kind of identity, and deal with [Foreign Function Interfaces](https://en.wikipedia.org/wiki/Foreign_function_interface) for non *JS* related programming languages.\n\n\n## jsProxy\n\n### `jsProxy.proxy.type(value, ...rest)`\n\nThe `proxy` literal will contain all defined proxy types able to bootstrap related proxies directly.\n\nThe definition can contain any valid object literal key, including symbols.\n\n\u003ch4\u003eExample\u003c/h4\u003e\n\n```js\nimport define from 'js-proxy';\n\nconst secret = Symbol('secret');\n\nconst { proxy } = define({\n  object: {\n    // ... one or more traps ...\n  },\n  custom: {\n    // ... one or more traps ...\n  },\n  [secret]: {\n    // ... one or more traps ...\n  },\n});\n\n// create 3 different proxies\nproxy.object({});   // typeOf(...) === \"object\"\nproxy.custom({});   // typeOf(...) === \"custom\"\nproxy[secret]({});  // typeOf(...) === secret\n```\n\n#### Dealing with primitives\n\nThe `proxy` namespace is able to bootstrap even primitives but with the following constraints:\n\n  * at least common traps must be well defined otherwise the *Reflect* fallback might fail\n  * if passed as primitive, the value will be proxied automatically as an `Object(primitive)` and there won't be any way to `release(primitive)` later on\n  * if passed as reference, it's still needed to define common traps\n\n#### Example\n\n```js\nimport define from 'js-proxy';\n\nconst { proxy, release } = define({\n  string: {\n    get(str, key) {\n      const value = str[key];\n      return typeof value === 'function' ?\n              value.bind(str) : value;\n    },\n    destruct(str) {\n      console.log(`wrap for ${str} released`);\n    },\n  },\n});\n\n// works but release won't be effective\nproxy.string('test').slice(0, 1); // \"t\"\nrelease('test'); // ⚠️ WRONG\n// throws: Invalid unregisterToken ('test')\n\n// this works better and it's possible to release\nconst wrap = Object('test'); // new String('test')\nproxy.string(wrap).slice(0, 1);\nrelease(wrap);  // 👍 OK\n// destruct trap won't ever be invoked\n```\n\n#### Dealing with foreign programming languages\n\nUsually most primitive types are exchanged as such in the *ForeignPL* to *JS* world, so that numbers are converted, boolean are converted, strings are (likely) converted (but they don't really need to be) but objects, arrays, and functions cannot really be converted retaining their reference in the *ForeignPL* counterpart.\n\nIf it's desired to both deal with these cases and have a way to `release(token)` later on, there are at least two different approaches:\n\n  * wrap that primitive identifier as object itself such as `{_ref: 123}`, requiring for each trap to extract that `_ref` each time\n  * retain the token a part, passing it as second proxy argument\n\nIt is really up to you how you prefer handling references to your current *foreign PL* but at least there are a couple of options.\n\n#### Example\n\n```js\nimport define from 'js-proxy';\n\nconst { proxy, release } = define({\n  object: {\n    destruct(ref) {\n      // {_ref: 123} in case No.1\n      // 456 in case No.2\n      console.log(ref, 'proxy is gone');\n    },\n  },\n});\n\n// case No.1\nconst trapped1 = {_ref: 123};\nlet proxied1 = proxy.object(trapped1);\nsetTimeout(release, 1000, trapped1);\n\n// case No.2\nconst trapped2 = 456;\nconst token2 = Object(456);\nlet proxied2 = proxy.object(trapped2, token2);\nsetTimeout(release, 1000, token2);\n```\n\n### `jsProxy.release(token)`\n\nThis utility is particularly handy for *FFI* related use cases or whenever an explicit `destroy()` or `destruct()` method is meant by the code that provides the proxy.\n\nWhen the token is known and released, the `destruct` trap won't happen again, effectively avoiding double invokes of potentially the same procedure.\n\nThe `token` reference is, by default, the same proxied object so that it's easy behind the scene to hold it internally and eventually procedurally release that reference from the garbage collector.\n\nUse cases could be a terminated worker that was holding delivered proxies or users defined explicit actions to signal some reference is not needed anymore and won't be accessed again.\n\n#### Example\n\n```js\nimport define from 'js-proxy';\n\nconst { proxy, release } = define({\n  direct: {\n    destruct(ref) {\n      console.log(ref, 'not used anymore');\n    }\n  }\n});\n\nconst myRef = {ref: 123};\nconst outProxy = proxy.direct(myRef);\n\n// any time later we want to drop myRef on GC\nrelease(myRef);\n```\n\n### `jsProxy.typeOf(unknown)`\n\nDifferently from the [typeof operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof), the `typeOf` utility does the following:\n\n  * it retrieves the current `typeof` of the generic, *unknown*, value\n  * if the resulting *type* is `\"object\"`:\n    * if the namespace had such type defined in it, it returns that brand name instead\n    * if the value is an *array*, it returns `\"array\"`\n    * if the value is `null`, it returns `\"null\"`\n  * otherwise returns the string that `typeof` originally returned\n\n\u003e [!Note]\n\u003e \n\u003e This utility is not necessarily that useful with this module but it's especially handy to branch out specific proxies handlers and behavior whenever the type of proxy is known in the namespace.\n\n#### Example\n\n```js\nimport define from 'js-proxy';\n\nconst { proxy, typeOf } = define({\n  object: {},\n  string: {},\n  promise: {},\n});\n\nconst object = proxy.object({});\nconst str = proxy.string(new String(''));\nconst promise = proxy.promise(Promise.resolve(true));\n\n// all true\ntypeOf(object) === \"object\";\ntypeOf(str) === \"string\";\ntypeOf(promise) === \"promise\";\n```\n\n\n### `jsProxy.valueOf(unknown)`\n\nIf a defined proxy handler has its own `valueOf` trap, this utility will call that trap directly and return whatever that method decided to return.\n\nIt's literally a transparent *pass through* operation that will not involve native traps.\n\nIf the handler did not provide its own `valueOf` trap, this utility simply perform a `ref.valueOf()` operation.\n\n#### Example\n\n```js\nimport define from 'js-proxy';\n\nconst identity = value =\u003e value;\n\nconst { proxy, valueOf } = define({\n  object: {\n    valueOf: identity,\n  },\n  direct: {\n    valueOf: identity,\n  },\n  unknown: {},\n});\n\nconst object = proxy.object(123);\nconst array = [1, 2, 3];\nconst direct = proxy.direct(array);\nconst unknown = proxy.unknown([4, 5, 6]);\n\n// all true\nvalueOf(object) === 123;\nvalueOf(direct) === array;\nvalueOf(unknown) === unknown;\n```\n\n\n## MITM\n\nThe [MITM](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) utility puts a proxy handler behind the reference and not upfront. Usually, proxies are transparent until they are not:\n\n  * DOM operations are not allowed with proxies\n  * `typeof` or `isArray` or anything else drilling the proxied type might reveal the proxy or fail\n  * references need to be proxied before others can consume these, as opposite of hooking any extra feature/utility/observability without requiring 3rd party to change their reference to the real target\n\nAccordingly, the *MITM* export allows anything to have a proxy between its reference and its prototype, which requires extra careful handling, but it can be summarized as such:\n\n```js\nimport mitm from 'js-proxy/mitm';\n\n// generic DOM handler for text property\nconst textHandler = {\n  get(__proto__, name, target) {\n    if (name === 'text')\n      return target.textContent;\n    return Reflect.get(__proto__, name, target);\n  },\n  set(__proto__, name, value, target) {\n    if (name === 'text') {\n      target.textContent = value;\n      return true;\n    }\n    return Reflect.set(__proto__, name, value, target);\n  }\n};\n\n// pollute (once) any DOM node\nmitm(document.body, textHandler);\n\n// see magic\ndocument.body.text = 'Hello MITM';\ndocument.body.text; // 'Hello MITM'\n```\n\nThe rule of thumb for *MITM* is that last come is the first to intercept but it's possible to add multiple *MITM* although performance will degrade proportionally as more logic will be involved per each property.\n\n\n## Heap\n\nAs extra utility, the `js-proxy/heap` exports is particularly useful for cross realm *JS* proxied interactions.\n\nAs example, if your worker, or your main, would like to expose a reference to another worker or main thread, it is possible to associate the current reference to a unique identifier that can then be destroyed once the other world won't need it anymore.\n\n#### Example\n\n```js\nimport { drop, get, hold } from 'js-proxy/heap';\n\nlet thisWorldReference = {};\n\n// traps forever the reference until drop\nlet refID = hold(thisWorldReference);\n// it's always unique by reference\n// hold(thisWorldReference) === hold(thisWorldReference)\n\npostMessage({ type: 'object', value: refID });\n\naddEventListener('message', ({ data }) =\u003e {\n  const { value, trap, args } = data;\n  // drop the reference, not needed out there anymore\n  if (trap === 'destruct') {\n    drop(value);\n  }\n  else {\n    // retrieve the original reference by id\n    const ref = get(value);\n    postMessage(Reflect[trap](ref, ...args));\n  }\n});\n```\n\nIn the outer world, the `proxy.object({_ref: value})` could forward back via `postMessage` all traps, including the `destruct` when it happens, so that the worker can apply and reply with the result.\n\nAs summary, this export helps relating any reference to a unique identifier and it holds such reference until it's dropped. This is particularly useful to avoid the current realm collecting that reference, as it might be used solely in the outer world, still enabling, via `destruct` ability, to free memory on occasion.\n\n\n## Traps\n\nThe `js-proxy/traps` exports the following:\n\n```js\n// Standard Proxy Traps\nexport const APPLY                        = 'apply';\nexport const CONSTRUCT                    = 'construct';\nexport const DEFINE_PROPERTY              = 'defineProperty';\nexport const DELETE_PROPERTY              = 'deleteProperty';\nexport const GET                          = 'get';\nexport const GET_OWN_PROPERTY_DESCRIPTOR  = 'getOwnPropertyDescriptor';\nexport const GET_PROTOTYPE_OF             = 'getPrototypeOf';\nexport const HAS                          = 'has';\nexport const IS_EXTENSIBLE                = 'isExtensible';\nexport const OWN_KEYS                     = 'ownKeys';\nexport const PREVENT_EXTENSION            = 'preventExtensions';\nexport const SET                          = 'set';\nexport const SET_PROTOTYPE_OF             = 'setPrototypeOf';\n\n// Custom (JS)Proxy Traps\nexport const DESTRUCT                     = 'destruct';\nexport const VALUE_OF                     = 'valueOf';\n```\n\n\n## Types\n\nThe `js-proxy/types` exports the following:\n\n```js\nexport const ARRAY     = 'array';\nexport const BIGINT    = 'bigint';\nexport const BOOLEAN   = 'boolean';\nexport const FUNCTION  = 'function';\nexport const NULL      = 'null';\nexport const NUMBER    = 'number';\nexport const OBJECT    = 'object';\nexport const STRING    = 'string';\nexport const SYMBOL    = 'symbol';\nexport const UNDEFINED = 'undefined';\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebreflection%2Fjs-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebreflection%2Fjs-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebreflection%2Fjs-proxy/lists"}