{"id":20030113,"url":"https://github.com/swivelgames/underwriter","last_synced_at":"2026-05-01T21:02:09.730Z","repository":{"id":65939615,"uuid":"602924702","full_name":"Swivelgames/underwriter","owner":"Swivelgames","description":"A simple, yet powerful, Promise Registry.","archived":false,"fork":false,"pushed_at":"2023-03-07T23:31:38.000Z","size":636,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-08-19T02:22:48.141Z","etag":null,"topics":["dependency-injection","dependency-manager","fetch","framework","javascript","javascript-library","nodejs","npm","npm-package","promise"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Swivelgames.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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":"2023-02-17T08:34:45.000Z","updated_at":"2023-03-02T22:50:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"9385a68a-2694-406c-9f94-6a5c12955861","html_url":"https://github.com/Swivelgames/underwriter","commit_stats":{"total_commits":24,"total_committers":1,"mean_commits":24.0,"dds":0.0,"last_synced_commit":"cd1c50196271511b1261a8c7b0906d5cba2b7511"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/Swivelgames/underwriter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Swivelgames%2Funderwriter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Swivelgames%2Funderwriter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Swivelgames%2Funderwriter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Swivelgames%2Funderwriter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Swivelgames","download_url":"https://codeload.github.com/Swivelgames/underwriter/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Swivelgames%2Funderwriter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32512670,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["dependency-injection","dependency-manager","fetch","framework","javascript","javascript-library","nodejs","npm","npm-package","promise"],"created_at":"2024-11-13T09:24:42.225Z","updated_at":"2026-05-01T21:02:09.712Z","avatar_url":"https://github.com/Swivelgames.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Underwriter\n\nA simple, yet powerful, Promise Registry.\n\n- [How It Works](#how-it-works)\n- [Quick Usage](#quick-usage)\n  - [Example](#example)\n- [Options](#options)\n  - [_`function`_ `options.retriever` _Required_](#function-optionsretriever-required)\n  - [_`function`_ `options.initializer` _Optional_](#function-optionsinitializer-optional)\n  - [_`Promise`_ `option.defer` _Optional_](#promise-optiondefer-optional)\n    - [Wait to Retrieve](#wait-to-retrieve)\n    - [Retrieve Now, But Wait to Initialize](#retrieve-now-but-wait-to-initialize)\n  - [Advanced/Experimental](#advancedexperimental)\n    - [_`prototype/class`_ `options.thenableApi` _Experimental_](#-prototypeclass-optionsthenableapi-experimental)\n    - [_`boolean`_ `options.publicFulfill` _Experimental_](#-boolean-optionspublicfulfill-experimental)\n- [Test Coverage](#test-coverage)\n- [Lifecycle Diagram](#lifecycle-diagram)\n\n## How It Works\n\nUnderwriter provides access to Guarantors, which are simple interfaces for\nretrieving Promises by name. These promises can either be Resolved with a\n`guarantee` or Rejected with an error.\n\n1. A Guarantor is basically an object that holds Promises\n2. Each Promise (guarantee) has a name (identifier)\n3. When you call `.get(identifier)`, you get a Promise back, and the\n`retriever()` is called\n4. Once `retriever()` fetches the value, an optional `initializer()` can be used\nto parse/initialize the value\n5. Once that is complete, the value is given to anyone who calls `.get(identifier)`\n\nIn more ambiguous terms (`:^)`), Underwriter uses `Guarantors` to provide\nconsumers with a method of retrieving named `guarantees` from its registries.\n\nGuarantors are general purpose, and can be used for anything, from\nasynchronously importing ESM modules using `import()`, one-time retrieval of\nstatic resources from an API/CDN/wherever, or anything else you can think of. It\ncan even be used for retrieving interfaces for things already loaded in your\nenvironment, like UI Components, Controllers, Models, Stores, Actions, et cetera.\n\n## Quick Usage\n\n```javascript\nimport Guarantor from \"underwriter\";\nconst options = { retriever: (identifier) =\u003e fetchSomething(identifier) };\nconst guarantor = new Guarantor(options);\nconst resource = await guarantor.get(identifier);\n```\n\n1. Create a Guarantor (registry)\n2. Supply a `retriever(identifier: string): Promise\u003cguarantee\u003e`\n3. Request a `guarantee` with `Guarantor.get(identifier)`\n\n### Example\n\n#### Remote Resource\n\n```javascript\n// /configs/api.json\n{\n        configVersion: \"2.3.15\",\n        config: {\n                apiEndpoint: \"/api/\",\n                apiVersion: \"v1\"\n        }\n}\n```\n\n#### Guarantor\n\n```javascript\nimport Guarantor from \"underwriter\";\n\nconst retriever = (identifier) =\u003e (\n        fetch(`/configs/${identifier}.json`).then(\n                (response) =\u003e response.json()\n        )\n);\n\n// Optional\nconst initializer = (identifier, guarantee) =\u003e guarantee.config;\n\nconst configGuarantor = new Guarantor({\n        retriever,\n        initializer,\n});\n\nconst apiConfig = await configGuarantor.get('api');\n```\n\n## Options\n\n### _`function`_ `options.retriever` _Required_\n\nTypeScript notation:\n\n```typescript\ntype retriever = (identifier: string): Promise\u003cany\u003e;\n```\n\nThe `retriever()` can be any function that accepts an `identifier` and resolves\nwith a promise once the `guarantee` has been retrieved.\nThe `retriever` option is a function that is called whenever `.get(identifier)`\nis called. It is given an `identifier` and expected to retrieve the resource and\nreturn it in the form of a promise. This may take the form of a Fetch/XHR/AJAX\nrequest, an `import()`, or as simply mapping the `identifier` to the `key` of\nan object.\n\nFor those that prefer TypeScript-like notation, the `retriever()` should follow\nsomething like:\n\n### _`function`_ `options.initializer` _Optional_\n\nTypeScript notation:\n\n```typescript\ntype initializer = (identifier: string, guarantee: any): any;\n```\n\nThe `initializer` is given the `identifier` and `guarantee` value after the\nresource is retrieved. It's role is to prepare the value for usage. Whatever\nit returns will be the value that is given to anyone who has requested this\nguarantee with `.get(identifier)`. This could conceivably be a santization\nfunction, a function that calls `JSON.parse()` on the input, or constructs a\nnew class based on the data (e.g., `(id, guarantee) =\u003e new Foobar(guarantee)`).\n\n### _`Promise`_ `option.defer` _Optional_\n\nIf you need a Guarantor to wait before it `retrieves` or `initializes` your\nguarantees, you can use the `defer` option, which takes a Promise (or any\nThenable), and waits for it to resolve before continuing. This can be useful\nif you need to setup your application or retrieve things before you want the\nGuarantor to start retrieving or initializing values.\n\n#### Wait to Retrieve\n\nThis is the default functionality. By passing `option.defer` as a Promise, the\nGuarantor will not call the `retriever()` until the `option.defer` promise has\nresolved.\n\n```javascript\nconst defer = startupProcess(); // Promise\nconst guarantor = new Guarantor({ retriever, defer });\n// Won't retrieve until startupProcess is resolved\nconst foobar = await guarantor.get('foobar');\n```\n\nA hypothetical sitation might be when you require authentication (like a `JWT`)\nbefore your Guarantor will be able to retrieve anything. In this scenario, you\nwould resolve your `option.defer` promise once you have retrieved your\nhypothetical `JWT`.\n\n#### Retrieve Now, But Wait to Initialize\n\n**_`boolean`_ `option.retrieveEarly` _Optional_**\n\nThis changes the behavior of `option.defer` by allowing the Guarantor to call\nthe `retriever()` immediately, but defers the call to the `initializer()`\nuntil the `option.defer` promise has resolved.\n\n```javascript\nconst defer = startupProcess(); // Promise\nconst guarantor = new Guarantor({\n        retriever,\n        retrieveEarly: true, /* \u003c\u003c\u003c */\n        initializer,\n        defer,\n});\n// Retrieves immediately, but doesn't initialize() or\n// resolve until after startupProcess is resolved\nconst foobar = await guarantor.get('foobar');\n```\n\nSetting this to `true` is theoretically faster, because the Guarantor doesn't\nwait on anything to retrieve the resources and can do so asynchronously while\nthe application sets itself up. But it will only initialize those values once\nthe `parent` promise resolves.\n\n### Advanced/Experimental\n\n#### 🧪 _`prototype/class`_ `options.thenableApi` _Experimental_\n\nThe `options.thenableApi` feature allows you to specify a Promise\nimplementation different than the built-in, which should give you flexibility\nin the types of Promises you're working with. For instance, official support\nfor the novel [`thenable-events`](https://www.npmjs.com/package/thenable-events)\nPromise implementation is expected in the near future.\n\n#### 🧪 _`boolean`_ `options.publicFulfill` _Experimental_\n\nBy default, a `retriever` will execute when a Guarantee is requested, and the\nreturn value of this `retriever` will be used to `initialize` and then `fulfill`\nthe Guarantee. However, if there are times when you would like to `fulfill` a\nGuarantee outside of the standard lifecycle, you can do so by setting\n`publicFulfill` to `true`, which will give you a method for fulfilling a\nGuarantee ad-hoc:\n\n```typescript\ntype Guarantor.fulfill = (identifier: string, guarantee: any): Promise\u003cany\u003e;\n```\n\nExecuting this function will pass the `identifier` and `guarantee` to the\noptional `initializer`, and then fulfill the Guarantee.\n\n\u003e **Note:** _Guarantees can only be fulfilled once. Attempting to fulfill a\n\u003e Guarantee outside of the standard lifecycle may cause a rejection if the\n\u003e Guarantee has already been fulfilled._\n\n| :warning: This may change behavior in an unexpected manner. |\n| --- |\n\nPlease be aware of the differences in behavior outlined below before using this\noption.\n\n| `publicFulfill` | Changes |\n| -------------- | :------ |\n| `true`         | \u003cul\u003e\u003cli\u003eA previously non-existent `Guarantor.fulfill()` method appears.\u003c/li\u003e\u003cli\u003eThe `retriever` option becomes optional.\u003c/li\u003e\u003cli\u003eIf the `retriever()` returns `undefined` for a particular identifier, the guarantee _will not be fulfilled with a value of `undefined`_, and instead wait for the manual invocation of `Guarantor.fulfill()` to fulfill the promise (see fulfill syntax above).\u003c/li\u003e\u003c/ul\u003e |\n| `false`        | \u003cul\u003e\u003cli\u003eIf `options.publicFulfill = false`, a warning is now outputted informing the developer that **_the guarantee will successfully be fulfilled with a value of `undefined`_**, which may be unintended.\u003c/li\u003e\u003c/ul\u003e |\n\nThis behavior is currently being debated. Please refer to the issue ticket, or\ncreate one, to discuss.\n\n## Test Coverage\n\n```mocha\n  underwriter::Guarantor\n    underwriter::constructor()\n      ✔ should throw if no retriever is passed\n      ✔ should NOT throw if optional properties are omitted from options\n      ✔ should throw if an invalid retriever is passed\n      ✔ should throw if an invalid defer is passed\n      ✔ should throw if an invalid initializer is passed\n      ✔ should throw if an invalid Thenable API is passed\n      ✔ should NOT expose a fulfill method if publicFulfill option is omitted\n      ✔ should NOT expose a fulfill method if publicFulfill option is false\n      ✔ should expose a fulfill method if publicFulfill option is true\n      ✔ should NOT throw if publicFulfill is true and no retriever is passed\n    underwriter:get( identifier )\n      ✔ should reject if no identifier is passed\n      ✔ should reject if an invalid identifier is passed\n      ✔ should reject if the retriever fails\n      ✔ should create a promise and call the retriever\n      ✔ should not produce new promises or call the retriever again on subsequent calls\n      ✔ should wait to retrieve a guarantee until defer promise resolves if retrieveEarly is false\n      ✔ should immediately retrieve a guarantee if retrieveEarly is true\n      ✔ should fulfill even if retriever returns void\n      ✔ should NOT fulfill if retriever returns void, but publicFulfill is true\n      ✔ should not call the retriever if it was omitted and publicFulfill is true\n    underwriter:fulfill( identifier, guarantee )\n      ✔ should fulfill the guarantee matching the identifier\n\n  underwriter::utils\n    formatName()\n      ✔ should return lowercased version of a string\n      ✔ should cast other types to string\n    initializeIfNeeded()\n      ✔ should initialize a missing key\n      ✔ should initialize a missing key with a specific value factory\n      ✔ should preserve the original value if a key already exists\n\n\n  30 passing (98ms)\n\n--------------|---------|----------|---------|---------|-------------------\nFile          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s\n--------------|---------|----------|---------|---------|-------------------\nAll files     |     100 |      100 |     100 |     100 |\n copy.js      |     100 |      100 |     100 |     100 |\n fulfill.js   |     100 |      100 |     100 |     100 |\n guarantor.js |     100 |      100 |     100 |     100 |\n utils.js     |     100 |      100 |     100 |     100 |\n--------------|---------|----------|---------|---------|-------------------\n```\n\n## Lifecycle Diagram\n\nHere's a bonus for you: A horribly crude and probably unhelpful lifecycle\ndiagram that looks like it was put together by a 5 year old :)\n\n```diagram\ncall  ╔═════════════════════╗      ┌┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐\n⇢┈┈⇢┈ ║ Guarantor.get( id ) ║      ┊  (some local or remote resource)  ┊\n      ╚══════════╤══════════╝      └┈┈┈┈┈┈┈┬┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┬┈┈┈┈┈┈┈┘\n                 │                         │                   │\n                 │              ┌──────────↑───────────────────↓───────┐\n                 │          (pending)      ↑                   │       │\n return    ╔═══════════╗⇢┈┈┈┈⇢┈ │── options.retriever(id)      │       │\n⇠┈┈┈┈┈┈┈┈⇠ ║ *Promise  ║        │                              ↓       │\n           ╚═══════════╝┈⇠┈┈┈┈⇠ │←─ options.intializer(id, resource)   │\n                           (fulfilled)                                 │\n                                └──────────────────────────────────────┘\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswivelgames%2Funderwriter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fswivelgames%2Funderwriter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fswivelgames%2Funderwriter/lists"}