{"id":17262856,"url":"https://github.com/jpwilliams/distributed-promise","last_synced_at":"2025-04-12T19:53:30.139Z","repository":{"id":38983770,"uuid":"231816144","full_name":"jpwilliams/distributed-promise","owner":"jpwilliams","description":"Distribute a promise across multiple processes connected via Redis.","archived":false,"fork":false,"pushed_at":"2025-04-08T11:02:40.000Z","size":506,"stargazers_count":7,"open_issues_count":31,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-12T19:53:23.671Z","etag":null,"topics":["cache","distributed","memo","promise","redis","shared"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/jpwilliams.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":"2020-01-04T19:20:15.000Z","updated_at":"2024-09-16T15:31:11.000Z","dependencies_parsed_at":"2023-10-03T00:51:27.047Z","dependency_job_id":"2881ef16-17fe-4cea-a0bc-b0dd984424c9","html_url":"https://github.com/jpwilliams/distributed-promise","commit_stats":{"total_commits":154,"total_committers":6,"mean_commits":"25.666666666666668","dds":0.5714285714285714,"last_synced_commit":"74e799bcf63d53a340a99cee9d4b44963470964f"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpwilliams%2Fdistributed-promise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpwilliams%2Fdistributed-promise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpwilliams%2Fdistributed-promise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpwilliams%2Fdistributed-promise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jpwilliams","download_url":"https://codeload.github.com/jpwilliams/distributed-promise/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248625510,"owners_count":21135513,"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":["cache","distributed","memo","promise","redis","shared"],"created_at":"2024-10-15T07:54:53.342Z","updated_at":"2025-04-12T19:53:30.101Z","avatar_url":"https://github.com/jpwilliams.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @jpwilliams/distributed-promise\n\nDistribute a promise across multiple processes connected via Redis. If two seperate processes make the same call, only one will actually do the work, but both promises will return simultaneously with the same data.\n\nHas pretty types and works with any data compatible with `JSON.stringify` and `JSON.parse`.\n\n``` sh\nnpm install --save @jpwilliams/distributed-promise\n```\n\n``` js\nimport { DistributedPromiseWrapper } from '@jpwilliams/distributed-promise'\n\nconst wrapper = new DistributedPromiseWrapper({\n\tredis: myRedisClient\n})\n\n// any function\n// can be synchronous or asynchronous\nfunction joinStr (...strs) {\n\treturn strs.join()\n}\n\nconst sharedJoinStr = wrapper.wrap(joinStr)\nconst result = await sharedJoinStr('foo', 'bar')\n// result = 'foobar'\n```\n\n## Why?\n\nThis facilitates the [memoisation](https://en.wikipedia.org/wiki/Memoization) of expensive function calls in a distributed system and helps combat the issue of a [cache stampede](https://en.wikipedia.org/wiki/Cache_stampede).\n\nIt does this by only allowing a single service per job to actually do the work and makes all other services wait for _that_ result and _not_ compute their own.\n\n## How\n\nFirst, after creating a wrapper via `new DistributedPromiseWrapper`, you can wrap any functions for use with the library, regardless of what they return. The wrapping process will _always_ return a function that returns a Promise, even if the original function did not.\n\n``` js\n// (...objs: object[]): object\nconst combine = (...objs: object[]): object =\u003e Object.assign(...objs)\n\n// (...objs: object[]): Promise\u003cobject\u003e\nconst sharedCombine = wrapper.wrap(combine)\n```\n\nInternally, when a wrapped function is called, a simple route is followed:\n\n1. Get relevant data from cache based on input args. Found data? Return it.\n2. Try to attain a lock to get permission to do the work locally.\n3. If we get the lock, perform work locally, push the result to the cache, publish via Redis and return.\n4. If we didn't get the lock, wait for the data to be published via Redis and return it when it arrives.\n\nIn basic terms, if\n\n## API\n\n### `new DistributedPromiseWrapper(config: DistributedPromiseConfig)`\n\nCreates a new wrapper to use to wrap functions.\n\n**config** `DistributedPromiseConfig`\n- `redis: RedisClient` The `RedisClient` instance to use to connect.\n- `lockTimeout?: number` The amount of time in milliseconds to hold the Redis lock for when doing work locally. Defaults to `30000` (30 seconds).\n- `ttl?: number` The amount of time in milliseconds before items expire from the cache. Defaults to `1800000` (30 minutes).\n- `keyPrefix?: string` The prefix to use for all keys the library uses in Redis. Defaults to `distributed-promise`.\n- `lockPrefix?: string` The prefix to use for locks in Redis. Defaults to `lock`.\n- `notifPrefix?: string` The prefix to use for notifications in Redis. Defaults to `notif`.\n- `keySeperator?: string` The seperator to use between the segments of key data in Redis. Defaults to `:`.\n\n**Returns** `DistributedPromiseWrapper`.\n\n---\n\n### `DistributedPromiseWrapper.wrap(work: InputFn, config?: WrapConfig | string)`\n\nWraps a function, ready to share. An internal key is needed for caching and data retrieval. If just `work` is passed, this key will be the `name` of the function. If it does not have one, the library will throw.\n\n**work** `InputFn`\n\nA function. `(...args: any) =\u003e any` - any arguments and any return.\n\n**config** `WrapConfig | string`\n\nIf undefined, the internal key required will be grabbed from the `name` of the `work` function. If it does not have a name, the library will throw.\n\nIf a `string`, the internal key will be set to that string.\n\nTo customise your wrap further, you can send a `WrapConfig`:\n\n- `key: string` The internal key to use.\n- `timeout?: number` The timeout to wait for an external process to do the work before giving up and rejecting the promise.\n\n**Returns** your wrapped function.\n\n## Caveats\n\nKeep in mind that this library can only deal with raw data; you can't make a database connection and magically share it with everyone. ;)\n\n## Todo\n\n- [ ] Detect if work is already happening locally and tap in to the local Promise rather than going to Redis.\n- [ ] If a TTL of `0` is set, still look to Redis for locking and the receipt of data via pubsub, but never actually cache the data.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpwilliams%2Fdistributed-promise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjpwilliams%2Fdistributed-promise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpwilliams%2Fdistributed-promise/lists"}