{"id":18401974,"url":"https://github.com/vimtor/generic-resource-pool","last_synced_at":"2026-02-02T11:13:27.883Z","repository":{"id":244558936,"uuid":"815133983","full_name":"vimtor/generic-resource-pool","owner":"vimtor","description":"Atomically access any object array","archived":false,"fork":false,"pushed_at":"2024-11-18T08:44:06.000Z","size":98,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-02T06:02:03.420Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/vimtor.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-06-14T12:28:09.000Z","updated_at":"2024-11-18T08:40:04.000Z","dependencies_parsed_at":null,"dependency_job_id":"ed2c82b0-0dc7-4df5-bdaa-a867c83e7f8c","html_url":"https://github.com/vimtor/generic-resource-pool","commit_stats":null,"previous_names":["vimtor/generic-resource-pool"],"tags_count":2,"template":false,"template_full_name":"egoist/ts-lib-starter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vimtor%2Fgeneric-resource-pool","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vimtor%2Fgeneric-resource-pool/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vimtor%2Fgeneric-resource-pool/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vimtor%2Fgeneric-resource-pool/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vimtor","download_url":"https://codeload.github.com/vimtor/generic-resource-pool/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247612227,"owners_count":20966695,"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":[],"created_at":"2024-11-06T02:40:41.537Z","updated_at":"2026-02-02T11:13:27.843Z","avatar_url":"https://github.com/vimtor.png","language":"TypeScript","readme":"# generic-resource-pool\n\nAtomically access any collection of objects.\n\n[![npm version](https://badgen.net/npm/v/generic-resource-pool)](https://npm.im/generic-resource-pool) [![npm downloads](https://badgen.net/npm/dm/generic-resource-pool)](https://npm.im/generic-resource-pool) [![CI](https://github.com/vimtor/generic-resource-pool/actions/workflows/ci.yml/badge.svg)](https://github.com/vimtor/generic-resource-pool/actions/workflows/ci.yml)\n\n## Motivation\n\nWhen building [Crack \u0026 Stack](https://crackandstack.com) I needed a way to have a pool of web3 wallets that could be accessed atomically across multiple workers to prevent two processes from using the same wallet at the same time. I couldn't find a library that did this, so I built one.\n\nThe library is designed to be generic and can be used to pool any type of array object. It supports multiple drivers for the locking mechanism `memory`, `redis` and `dynamodb`. You can also create your own driver by implementing the `LockDriver` interface.\n\n## Install\n\n```bash\nnpm i generic-resource-pool\n```\n\nIf you want to use the Redis driver, you will need to install `ioredis`:\n\n```bash\nnpm i ioredis\n```\n\nIf you want to use the DynamoDB driver, you will need to install `@aws-sdk/client-dynamodb`:\n\n```bash\nnpm i @aws-sdk/client-dynamodb\n```\n\n## Usage\n\n```typescript\nimport { ResourcePool } from 'generic-resource-pool';\nimport { RedisDriver } from 'generic-resource-pool/redis';\n\nconst users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];\n\nconst pool = new ResourcePool({\n  driver: new RedisDriver({ host: 'localhost', port: 6379 }),\n  resources: users,\n  key: (user) =\u003e user.id,\n  expires: 1000,\n  interval: 500,\n  shuffle: true,\n});\n\nconst user = await pool.acquire({ timeout: 5000 });\nif (user) {\n  // Do something with the user\n  \n  // Optionally release the user back to the pool\n  await pool.release(user); \n}\n```\n\nYou can find more details in the [API](#API) section.\n\n## Drivers\n\n### Memory\n\nThe memory driver does not require any additional dependencies. It's useful for testing and development. It can be used on production if you don't need to share the pool across multiple processes.\n\n```typescript\nimport { ResourcePool } from 'generic-resource-pool';\nimport { MemoryDriver } from 'generic-resource-pool/memory';\n\nconst pool = new ResourcePool({\n  driver: new MemoryDriver(),\n  // ...\n});\n```\n\n### Redis\n\nThe Redis driver uses [ioredis](https://github.com/redis/ioredis) to lock resources. The driver accepts a configuration object or an existing Redis client.\n\n```typescript\nimport { ResourcePool } from 'generic-resource-pool';\nimport { RedisDriver } from 'generic-resource-pool/redis';\n\nconst pool = new ResourcePool({\n  driver: new RedisDriver({ host: 'localhost', port: 6379 }),\n  // ...\n});\n```\n\nIf you have an existing Redis client, you can pass it to the driver:\n\n```typescript\nimport { ResourcePool } from 'generic-resource-pool';\nimport { RedisDriver } from 'generic-resource-pool/redis';\nimport { Redis } from 'ioredis';\n\nconst redis = new Redis({ host: 'localhost', port: 6379 });\n\nconst pool = new ResourcePool({\n  driver: new RedisDriver(redis),\n  // ...\n});\n```\n\n### DynamoDB\n\nThe DynamoDB driver uses [@aws-sdk/client-dynamodb](https://github.com/aws/aws-sdk-js-v3) to lock resources. The driver accepts a configuration object or an existing DynamoDB client along with the table name. If the table does not exist, it will be created automatically.\n\n```typescript\nimport { ResourcePool } from 'generic-resource-pool';\nimport { DynamoDBDriver } from 'generic-resource-pool/dynamodb';\n\nconst pool = new ResourcePool({\n  driver: new DynamoDBDriver({\n    region: 'us-east-1',\n  }, 'ResourcePool'),\n  // ...\n});\n```\n\n## API\n\n### `new ResourcePool(options: ResourcePoolOptions)`\n\n- `options.driver: LockDriver` - The driver to use for the locking mechanism.\n- `options.resources: T[]` - Array of resources in the pool.\n- `options.key: (resource: T) =\u003e string | Promise\u003cstring\u003e` - Function that returns a unique key for each resource.\n- `options.expires: number | ((resource: T) =\u003e number | Promise\u003cnumber\u003e)` - Time in milliseconds before a lock expires (also accepts a function that returns a number given the resource).\n- `options.interval: number | undefined` - Time in milliseconds between retries when trying to acquire a lock (default: 500).\n- `options.shuffle: boolean | undefined` - Shuffle the resources before trying to acquire a lock (default: true).\n\n#### `pool.acquire(options: AcquireOptions): Promise\u003cT | null\u003e`\n- `options.timeout: number | undefined` - Timeout in milliseconds to wait for a resource to become available. If undefined, it will only try once. (default: undefined).\n- Returns a promise that resolves to the acquired resource or `null` if the timeout is reached.\n\n#### `pool.release(resource: T): Promise\u003cboolean\u003e`\n- `resource: T` - The resource to release.\n- Returns a promise that resolves to `true` if the resource was released successfully, `false` otherwise.\n\n### `interface LockDriver`\n\n- `lock(key: string, expires: number): Promise\u003cboolean\u003e` - Locks a resource.\n- `unlock(key: string): Promise\u003cboolean\u003e` - Unlocks a resource.\n\n## Examples\n\n### Web3 Wallet Pool\n\nIn this example, we have a pool of web3 wallets that we want to use to send transactions. We want to make sure that no two transactions are sent from the same wallet at the same time. We also want to make sure that a wallet is not used if it has pending transactions.\n\n```typescript\nimport { ResourcePool } from 'generic-resource-pool';\nimport { RedisDriver } from 'generic-resource-pool/redis';\nimport { Account, WalletClient } from \"viem\";\n\nexport class WalletPool\u003cWallet extends WalletClient\u003e extends ResourcePool\u003cWallet\u003e {\n  constructor(wallets: Wallet[], namespace: string) {\n    super({\n      driver: new RedisDriver(redis),\n      resources: wallets,\n      key: (wallet) =\u003e `app:wallets:${namespace}:${wallet.account?.address}`,\n      expires: environment.DEFAULT_TRANSACTION_TIMEOUT,\n    });\n  }\n\n  async acquire() {\n    const wallet = await super.acquire({ timeout: environment.DEFAULT_TRANSACTION_TIMEOUT });\n\n    if (!wallet) {\n      throw new Error(\"No wallets available\");\n    }\n\n    if (await this.hasPendingTransactions(wallet)) {\n      throw new Error(`Wallet ${wallet.account?.address} has pending transactions`);\n    }\n\n    return wallet;\n  }\n\n  private async hasPendingTransactions(wallet: Wallet) {\n    const [pending, current] = await Promise.all([\n      publicClient.getTransactionCount({\n        address: wallet.account.address,\n        blockTag: \"pending\",\n      }),\n      publicClient.getTransactionCount({\n        address: wallet.account.address,\n      }),\n    ]);\n\n    return pending - current \u003e 0;\n  }\n}\n\n```\n\n### Custom Driver\n\nYou can create your own driver by implementing the `LockDriver` interface. For example this is a driver using a custom PostgreSQL table and Drizzle ORM:\n\n```typescript\nimport { LockDriver } from 'generic-resource-pool';\nimport { pgTable, boolean, timestamp, text } from \"drizzle-orm/pg-core\";\n\nconst table = pgTable('locks', {\n  key: text(),\n  expires: timestamp(),\n});\n\nexport class PostgresDriver {\n  async lock(key: string, expires: number) {\n    try {\n      await db.insert(table).values({ key, expires: new Date(Date.now() + expires) });\n      return true;\n    } catch (error) {\n      const [resource] = await db.select().from(table).where(eq(table.key, key));\n      if (resource.expires \u003c new Date()) {\n        if (await this.unlock(key)) {\n          return this.lock(key, expires);\n        }\n      }\n      \n      return false;\n    }\n  }\n\n  async unlock(key: string) {\n    await db.delete(table).where(eq(table.key, key));\n    return true;\n  }\n}\n\nconst pool = new ResourcePool({\n  driver: new PostgresDriver(),\n  // ...\n});\n```\n\n## License\n\nMIT \u0026copy; [vimtor](https://github.com/sponsors/vimtor)\n","funding_links":["https://github.com/sponsors/vimtor"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvimtor%2Fgeneric-resource-pool","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvimtor%2Fgeneric-resource-pool","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvimtor%2Fgeneric-resource-pool/lists"}