{"_id":"@alternayte/queuebox-inbox","_rev":"3-841e6106086b040f9f80640116a376c7","name":"@alternayte/queuebox-inbox","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@alternayte/queuebox-inbox","version":"0.1.0","keywords":["queuebox","inbox","outbox","transactional","postgresql","sqlserver"],"license":"Apache-2.0","_id":"@alternayte/queuebox-inbox@0.1.0","maintainers":[{"name":"alternayte","email":"nate.andert@gmail.com"}],"homepage":"https://github.com/alternayte/queuebox#readme","bugs":{"url":"https://github.com/alternayte/queuebox/issues"},"dist":{"shasum":"8afd0d7ee6367079ca364aa51a23c9a5f1947962","tarball":"https://registry.npmjs.org/@alternayte/queuebox-inbox/-/queuebox-inbox-0.1.0.tgz","fileCount":8,"integrity":"sha512-3W+g/qRkDoeg6Y1hc6hX36nQ9kM5SAl9n1hhY8tC8k0sdwCXTOiEA97GTMsslLnrYXmyWk7lhGhaRKG+xnoMyQ==","signatures":[{"sig":"MEYCIQDPKISx2Lz2c5/OgFfobvARExn50UcOF/WxRk66zdn4PQIhAMVH3AU5+VvUW9tvgI58ZpDbE1DRAszPdazeUJFjzZPy","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alternayte%2fqueuebox-inbox@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":193062},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"9a843681c86d9382060aca1ce3944d5a56351101","scripts":{"test":"node --test test/unit/*.test.ts","build":"tsup","test:bun":"./run-bun-tests.sh","typecheck":"tsc --noEmit","test:contract":"node --test --test-timeout=600000 test/contract/*.test.ts"},"_npmUser":{"name":"alternayte","email":"nate.andert@gmail.com"},"repository":{"url":"git+https://github.com/alternayte/queuebox.git","type":"git","directory":"clients/typescript"},"_npmVersion":"10.9.8","description":"A pull-inbox worker for QueueBox. It claims, renews, completes, retries and dead-letters messages, and it runs the completion inside the handler's own transaction.","directories":{},"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"devDependencies":{"pg":"^8.13.1","tsup":"^8.3.5","mssql":"^11.0.1","@types/pg":"^8.11.10","typescript":"^5.7.2","@types/node":"^22.10.2","@types/mssql":"^12.3.0","testcontainers":"^12.1.0","@testcontainers/postgresql":"^12.1.0","@testcontainers/mssqlserver":"^12.1.0"},"_npmOperationalInternal":{"tmp":"tmp/queuebox-inbox_0.1.0_1788780385815_0.16593451632027256","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@alternayte/queuebox-inbox","version":"0.2.0","keywords":["queuebox","inbox","outbox","transactional","postgresql","sqlserver"],"license":"Apache-2.0","_id":"@alternayte/queuebox-inbox@0.2.0","maintainers":[{"name":"alternayte","email":"nate.andert@gmail.com"}],"homepage":"https://github.com/alternayte/queuebox#readme","bugs":{"url":"https://github.com/alternayte/queuebox/issues"},"dist":{"shasum":"a153333ce7370594525bf1265c9e46b55c3f9564","tarball":"https://registry.npmjs.org/@alternayte/queuebox-inbox/-/queuebox-inbox-0.2.0.tgz","fileCount":8,"integrity":"sha512-j6Z7/0/RYpDOTGOipX8DRMSGAtbSkovJkyaDrRPA7pDt7JIVPdJKOWDdLYbuYpUDNe7rRLl0dzzNv0UaX8X73w==","signatures":[{"sig":"MEUCIASPVjw1BT1p9yezjluLKsB+Ragv3tat3Dy+UHrwe/f6AiEA5IKtv38HmlzKJvKuLSrASSGE6RjgE49GVvvTvKI2oAs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alternayte%2fqueuebox-inbox@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":228583},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"818dca4007786e793be57c9561fcc7f75dd7bc28","scripts":{"test":"node --test test/unit/*.test.ts","build":"tsup","test:bun":"./run-bun-tests.sh","typecheck":"tsc --noEmit","test:contract":"node --test --test-timeout=600000 test/contract/*.test.ts"},"_npmUser":{"name":"alternayte","email":"nate.andert@gmail.com"},"repository":{"url":"git+https://github.com/alternayte/queuebox.git","type":"git","directory":"clients/typescript"},"_npmVersion":"10.9.8","description":"A pull-inbox worker for QueueBox. It claims, renews, completes, retries and dead-letters messages, and it runs the completion inside the handler's own transaction.","directories":{},"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"devDependencies":{"pg":"^8.13.1","tsup":"^8.3.5","mssql":"^11.0.1","@types/pg":"^8.11.10","typescript":"^5.7.2","@types/node":"^22.10.2","@types/mssql":"^12.3.0","testcontainers":"^12.1.0","@testcontainers/postgresql":"^12.1.0","@testcontainers/mssqlserver":"^12.1.0"},"_npmOperationalInternal":{"tmp":"tmp/queuebox-inbox_0.2.0_1789004018428_0.8218572610850001","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"_id":"@alternayte/queuebox-inbox@0.3.0","bugs":{"url":"https://github.com/alternayte/queuebox/issues"},"dist":{"shasum":"5d63344b42844fdd6de00dd715071e5fb0718a06","tarball":"https://registry.npmjs.org/@alternayte/queuebox-inbox/-/queuebox-inbox-0.3.0.tgz","fileCount":8,"integrity":"sha512-0V1S/LjTBJ3MQOVXkd8ygWx1Tl8GVjyAplMJmuRU4XfvaQ94CHuCUMV41YMkWMHbKmXrA0qndBFffSeinru8aA==","signatures":[{"sig":"MEUCIQD8PupJjq2gKCmSEKOqmvdoNnpNyAT7fjSOc7vy1OY9NAIgHM+aMi6Jc+tWNEScIE0KE0RQRzBvdHueNvM93aaUR/A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCm/s1UGPfQpvbDrZjZuJ+j3QFvoEbOPOeUhTUEbWCDwwIhAI3OF6pAYaVxjVrNBjI+PXLD8m3QiQI14uJWgCMM8zOb"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alternayte%2fqueuebox-inbox@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":229844},"main":"./dist/index.cjs","name":"@alternayte/queuebox-inbox","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"17fedc6296748bda16167aa6014e2fc6155ecaeb","license":"Apache-2.0","scripts":{"test":"node --test test/unit/*.test.ts","build":"tsup","test:bun":"./run-bun-tests.sh","typecheck":"tsc --noEmit","test:contract":"node --test --test-timeout=600000 test/contract/*.test.ts"},"version":"0.3.0","_npmUser":{"name":"alternayte","email":"nate.andert@gmail.com"},"homepage":"https://github.com/alternayte/queuebox#readme","keywords":["queuebox","inbox","outbox","transactional","postgresql","sqlserver"],"repository":{"url":"git+https://github.com/alternayte/queuebox.git","type":"git","directory":"clients/typescript"},"_npmVersion":"10.9.8","description":"A pull-inbox worker for QueueBox. It claims, renews, completes, retries and dead-letters messages, and it runs the completion inside the handler's own transaction.","directories":{},"maintainers":[{"name":"alternayte","email":"nate.andert@gmail.com"}],"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"devDependencies":{"pg":"^8.13.1","tsup":"^8.3.5","mssql":"^11.0.1","@types/pg":"^8.11.10","typescript":"^5.7.2","@types/node":"^22.10.2","@types/mssql":"^12.3.0","testcontainers":"^12.1.0","@testcontainers/postgresql":"^12.1.0","@testcontainers/mssqlserver":"^12.1.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/queuebox-inbox_0.3.0_1789472207116_0.9610083360880928"}}},"time":{"created":"2026-09-07T11:26:25.512Z","modified":"2026-09-15T11:36:47.554Z","0.1.0":"2026-09-07T11:26:25.944Z","0.2.0":"2026-09-10T01:33:38.567Z","0.3.0":"2026-09-15T11:36:47.230Z"},"bugs":{"url":"https://github.com/alternayte/queuebox/issues"},"license":"Apache-2.0","homepage":"https://github.com/alternayte/queuebox#readme","keywords":["queuebox","inbox","outbox","transactional","postgresql","sqlserver"],"repository":{"url":"git+https://github.com/alternayte/queuebox.git","type":"git","directory":"clients/typescript"},"description":"A pull-inbox worker for QueueBox. It claims, renews, completes, retries and dead-letters messages, and it runs the completion inside the handler's own transaction.","maintainers":[{"name":"alternayte","email":"nate.andert@gmail.com"}],"readme":"# @alternayte/queuebox-inbox\n\nA pull-inbox worker for [QueueBox](https://github.com/alternayte/queuebox).\n\nQueueBox makes the push path need no code. The pull path needed five SQL statements, a renewal\ntimer and a lease discipline, written by hand in every application. This package holds all of it.\n\n## What it guarantees\n\n**Your writes and the completion commit together.** That is the whole point of the pull path.\nThe handler receives the transaction, and the library runs the completion inside it. If the\ncompletion affects no row, the lease was lost, another worker owns the message, and the library\nrolls your writes back and reports nothing as done.\n\n## Requirements\n\n| | |\n|---|---|\n| Runtime | Node 22 or later, Bun, or Deno |\n| Database | PostgreSQL through `pg`, or SQL Server through `mssql` |\n| QueueBox | The V10 schema or later, which adds the inbox `headers` column |\n\nThe package depends on no driver. It states the small interface it needs and ships an adapter\nfor each, so one package serves both dialects and your application brings the driver it has.\n\n## Install\n\n```\nnpm install @alternayte/queuebox-inbox\n```\n\n## PostgreSQL\n\n```ts\nimport pg from \"pg\";\nimport { InboxWorker, fromPg } from \"@alternayte/queuebox-inbox\";\n\nconst pool = new pg.Pool({ connectionString: process.env.QUEUEBOX_DB });\n\nconst worker = new InboxWorker(fromPg(pool), { source: \"orders\", batchSize: 10, leaseMs: 30_000 });\n\nawait worker.run(async (message, tx) => {\n  const payload = message.payload as { id: string; total: number };\n\n  await tx.query(\"INSERT INTO orders (id, total) VALUES ($1, $2)\", [payload.id, payload.total]);\n});\n```\n\n## SQL Server\n\n```ts\nimport mssql from \"mssql\";\nimport { InboxWorker, fromMssql } from \"@alternayte/queuebox-inbox\";\n\nconst pool = await new mssql.ConnectionPool(process.env.QUEUEBOX_DB!).connect();\n\nconst worker = new InboxWorker(fromMssql(mssql, pool), { source: \"orders\", dialect: \"sqlserver\" });\n\nawait worker.run(async (message, tx) => {\n  const payload = message.payload as { id: string; total: number };\n\n  await tx.query(\"INSERT INTO orders (id, total) VALUES (@p1, @p2)\", [payload.id, payload.total]);\n});\n```\n\nThe `mssql` module itself is a parameter, because the adapter needs its `Transaction` and\n`Request` constructors and this package imports no driver.\n\nSet `mssql`'s request timeout to at least 30 seconds. Its default is 15 seconds, well\nbelow that. The SQL Server claim runs `sp_getapplock` with a 10 second lock timeout of\nits own, so the server always raises Msg 51000 before a request timeout of 30 seconds\nor more can abort the call. A client-side abort does not roll back the claim's\ntransaction, because the lock is held under `@LockOwner = 'Transaction'`. The symptom\nof a shorter request timeout is an abandoned application lock: the per-source claim\nlock stays held until the connection resets, and every later claim on that source\nstalls behind it.\n\n## Placeholders\n\nBoth dialects bind **positionally**, because `pg` accepts no named parameter. Write the\nplaceholder style of your own database: `$1` for PostgreSQL, `@p1` for SQL Server. The values\nare the second argument, in the order the statement names them.\n\n## What the handler receives\n\n| Field | Meaning |\n|-------|---------|\n| `message.id` | The inbox row identifier |\n| `message.source` | The source name |\n| `message.idempotencyKey` | The deduplication key. The full identity is the source and this key together |\n| `message.aggregateId` | Nullable |\n| `message.eventType` | Nullable |\n| `message.payload` | The JSON body, parsed |\n| `message.headers` | The stored headers, one string value per key |\n| `message.attempt` | Zero on the first delivery |\n| `message.correlationId` | Nullable, for logs |\n\nThe claim token is absent on purpose. The library owns the token, because a handler that could\nreach it could complete a message out of band.\n\n## Rules for a handler\n\n1. Write every application change through the transaction the handler receives.\n2. Do not commit and do not roll back. The library owns both.\n3. Throw to fail the message. The library rolls the transaction back, so no partial write stays.\n4. Honour the abort signal, which is the third argument. It aborts when the lease is lost and\n   when a shutdown runs out of grace.\n5. Do external work, such as an HTTP call, only where a repeat is safe. A transaction cannot roll\n   back a call to another system. Deduplicate on the source and the idempotency key.\n\n## Settings\n\n| Setting | Default | Meaning |\n|---------|---------|---------|\n| `source` | none, it is mandatory | The source whose messages this worker takes |\n| `batchSize` | 10 | The largest number of messages one claim takes |\n| `leaseMs` | 30000 | The lease duration. The renewal runs every third of it |\n| `maxConcurrency` | 1 | The largest number of handlers that run at one time |\n| `pollIntervalMs` | 1000 | The wait after a claim that returned nothing |\n| `shutdownGraceMs` | 30000 | How long a stop waits for the handlers already running |\n| `dialect` | `\"postgresql\"` | `\"postgresql\"` or `\"sqlserver\"` |\n| `schema` | the QueueBox names | The table and column names, when an operator mapped them |\n| `retryPolicy` | `defaultRetryPolicy()` | What happens to a message whose handler threw |\n| `logger` | none | The caller's logger. The library prints nothing without one |\n\nThe concurrency default is one. A handler meets no sibling message unless the caller raises it.\nRaise it only when the handler is safe against a sibling message of another aggregate:\n\n```typescript\nconst options = { source: \"orders\", maxConcurrency: 10 };\n```\n\n## Failure, retry and the dead letter\n\nThe default policy retries while `attempt < maxAttempts`, with an exponential backoff and\njitter, and dead-letters after that. Only your application knows that a validation error must\nnever be retried while a timeout must, so write your own:\n\n```ts\nimport { deadLetter, retryAfter } from \"@alternayte/queuebox-inbox\";\nimport type { InboxRetryPolicy } from \"@alternayte/queuebox-inbox\";\n\nconst policy: InboxRetryPolicy = (message, failure) => {\n  // A bad payload never becomes good. Do not spend five attempts on it.\n  if (failure instanceof SyntaxError) {\n    return deadLetter();\n  }\n\n  return message.attempt >= 5 ? deadLetter() : retryAfter(2 ** message.attempt * 1000);\n};\n```\n\n## Shutdown\n\nAbort the signal you passed to `run`. The worker stops claiming at once. The handlers that\nalready run keep `shutdownGraceMs`, and a handler that does not finish inside it is aborted. Its\nmessage is abandoned: the library completes nothing, spends no attempt on it, and lets the lease\nexpire so another worker takes it.\n\n## Logging\n\nPass a `logger`. Without one the library prints nothing. Every message and every error passes\nthrough the same redaction QueueBox itself uses, so a connection string password reaches neither\na log line nor the `last_error` column.\n\n## Mapped table and column names\n\nQueueBox lets an operator rename the inbox table and its columns. Name them here as well:\n\n```ts\nimport { defaultSchema } from \"@alternayte/queuebox-inbox\";\n\nconst worker = new InboxWorker(fromPg(pool), {\n  source: \"orders\",\n  schema: { ...defaultSchema, table: \"qb_messages\", state: \"row_state\" },\n});\n```\n\nEvery name is quoted and checked before it reaches the database, so a mapping cannot carry SQL.\n\n## Runtimes\n\nThe package ships ESM and CJS, and it uses no Node-only API in the hot path, so it runs on Node,\nBun and Deno. The constraint is the database driver, not the runtime. The unit tests run under\nNode and under Bun in continuous integration.\n\n## Releases\n\nThe library releases on its own tag, so a library fix never needs a QueueBox release.\n\n```bash\ngit tag typescript-v0.1.1 && git push origin typescript-v0.1.1\n```\n\n## License\n\nApache-2.0.\n","readmeFilename":"README.md"}