{"id":19793599,"url":"https://github.com/darky/pg-trx-outbox","last_synced_at":"2025-09-18T02:33:01.009Z","repository":{"id":153897417,"uuid":"631029764","full_name":"darky/pg-trx-outbox","owner":"darky","description":"Transactional outbox of Postgres for Node.js with little Event Sourcing","archived":false,"fork":false,"pushed_at":"2025-08-27T12:51:23.000Z","size":273,"stargazers_count":3,"open_issues_count":4,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-09T11:38:48.577Z","etag":null,"topics":["2pc","event","nodejs","outbox","pg","postgres","postgresql","sourcing","transaction","transactional"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/darky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2023-04-21T18:36:01.000Z","updated_at":"2025-08-27T12:51:26.000Z","dependencies_parsed_at":"2025-05-20T23:01:54.344Z","dependency_job_id":null,"html_url":"https://github.com/darky/pg-trx-outbox","commit_stats":null,"previous_names":["darky/pg-kafka-trx-outbox"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/darky/pg-trx-outbox","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darky%2Fpg-trx-outbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darky%2Fpg-trx-outbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darky%2Fpg-trx-outbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darky%2Fpg-trx-outbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/darky","download_url":"https://codeload.github.com/darky/pg-trx-outbox/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darky%2Fpg-trx-outbox/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275697285,"owners_count":25511592,"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","status":"online","status_checked_at":"2025-09-18T02:00:09.552Z","response_time":77,"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":["2pc","event","nodejs","outbox","pg","postgres","postgresql","sourcing","transaction","transactional"],"created_at":"2024-11-12T07:10:30.431Z","updated_at":"2025-09-18T02:33:00.985Z","avatar_url":"https://github.com/darky.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pg-trx-outbox\n\n![photo_2023-04-22_03-38-43](https://user-images.githubusercontent.com/1832800/234091651-2a496563-6016-45fa-96f6-0b875899fe7e.jpg)\n\nTransactional outbox of Postgres for Node.js with little Event Sourcing\u003cbr/\u003e\nMore info about Transactional Outbox pattern: https://microservices.io/patterns/data/transactional-outbox.html\u003cbr/\u003e\nMore info about Event Sourcing pattern: https://microservices.io/patterns/data/event-sourcing.html\n\n## DB structure\n\n```sql\nCREATE TABLE IF NOT EXISTS pg_trx_outbox (\n  id bigserial NOT NULL,\n  processed bool NOT NULL DEFAULT false,\n  created_at timestamptz NOT NULL DEFAULT now(),\n  updated_at timestamptz NOT NULL DEFAULT now(),\n  since_at timestamptz,\n  topic text NOT NULL,\n  \"key\" text NULL,\n  value jsonb NULL,\n  \"partition\" int2 NULL,\n  \"timestamp\" int8 NULL,\n  headers jsonb NULL,\n  response jsonb NULL,\n  error text NULL,\n  meta jsonb NULL,\n  context_id double precision NOT NULL DEFAULT random(),\n  attempts smallint NOT NULL DEFAULT 0,\n  is_event bool NOT NULL DEFAULT false,\n  error_approved boolean NOT NULL DEFAULT false,\n  CONSTRAINT pg_trx_outbox_pk PRIMARY KEY (id)\n);\n\nCREATE INDEX pg_trx_outbox_not_processed_idx\n  ON pg_trx_outbox (processed, id)\n  WHERE (processed = false);\n```\n\n## Types of DB entities\n\n### Command\n\n* Command it's row when `is_event = false`\n* Command handled by one consumer only once (at least once in theory), Postgres SQL `for update` used for row locking\n* Usually used for third-party integration\n* May propagate events or another commands in same transaction\n\n### Event\n\n* Event it's row when `is_event = true`\n* Same event wlll be handled by all consumers\n* Usually used for DB denormalization, cache creation, response providing, etc\n\n## Modes\n\n### Short polling mode\n\nMessages polled from PostgreSQL using `FOR UPDATE` with `COMMIT` order. This batch of messaged produced to destination. Then messages marked as `processed`. This mode used by default.\n\n#### Short polling example\n\n##### Code\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnAdapter(), // about adapters see below\n  outboxOptions: {\n    mode: 'short-polling',\n    pollInterval: 5000, // how often to poll PostgreSQL for new messages, default 5000 milliseconds\n    limit: 50, // how much messages in batch, default 50\n    onError(err) {/**/} // callback for catching uncaught error\n  },\n  eventSourcingOptions: {/* [2] */}\n});\n\nawait pgTrxOutbox.start();\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L85\n\n### Notify mode\n\nFor reducing latency PostgreSQL `LISTEN/NOTIFY` can be used. When message inserted in DB, `NOTIFY` called and all listeners try to fetch new messages.\n**Short polling** mode also used here, because `LISTEN/NOTIFY` not robust mechanism and notifications can be lost.\n\n#### Notify example\n\n##### Deps\n\n`npm install --save pg-listen`\n\n##### SQL migration\n\n```sql\nCREATE OR REPLACE FUNCTION pg_trx_outbox() RETURNS trigger AS $trigger$\n  BEGIN\n    PERFORM pg_notify('pg_trx_outbox', '{}');\n    RETURN NEW;\n  END;\n$trigger$ LANGUAGE plpgsql;\n\nCREATE TRIGGER pg_trx_outbox AFTER INSERT ON pg_trx_outbox\nEXECUTE PROCEDURE pg_trx_outbox();\n```\n\n##### Code\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnAdapter(), // about adapters see below\n  outboxOptions: {\n    mode: 'notify',\n    pollInterval: 5000, // how often to poll PostgreSQL for new messages, default 5000 milliseconds\n    limit: 50, // how much messages in batch, default 50\n    onError(err) {/**/} // callback for catching uncaught error\n  },\n  eventSourcingOptions: {/* [2] */}\n});\n\nawait pgTrxOutbox.start();\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L85\n\n### Manual trigger of events consuming\n\nYou can trigger events consuming manually via `pgTrxOutbox.fetchEvents()`.\n\n#### Manual trigger of events example\n\n##### Code\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnAdapter(), // about adapters see below\n  outboxOptions: {\n    mode: 'short-polling',\n    pollInterval: 5000, // how often to poll PostgreSQL for new messages, default 5000 milliseconds\n    limit: 50, // how much messages in batch, default 50\n    onError(err) {/**/} // callback for catching uncaught error\n  },\n  eventSourcingOptions: {/* [2] */}\n});\n\nawait pgTrxOutbox.start();\n\npgTrxOutbox.fetchEvents() // Trigger of events consuming manually\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L85\n\n## Create custom adapter for destination\n\nSome adapters for Transactional Outbox destination are built-in.\u003cbr/\u003e\nYou can find it here: https://github.com/darky/pg-trx-outbox/tree/master/src/adapters \u003cbr/\u003e\nBut sometimes you want to create your own\n\n#### Custom adapter example\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\nimport { Adapter } from 'pg-trx-outbox/dist/src/types'\n\nclass MyOwnAdapter implements Adapter {\n  async start() {\n    // some init logic here, establish connection and so on\n  },\n  \n  async stop() {\n    // some graceful shutdown logic here, close connection and so on\n  },\n  \n  async send(messages) {\n    // messages handling here\n    // need return Promise.allSettled compatible array for each message in appropriate order\n    \n    return [\n      // example of successful message handling\n      { value: {someResponse: true}, status: 'fulfilled' },\n      // example of exception while message handling\n      { reason: 'some error text', status: 'rejected' },\n      // Optional meta property can be returned\n      // which will be saved in appropriate DB column\n      { value: {someResponse: true}, status: 'fulfilled', meta: {someMeta: true} }\n    ]\n  },\n\n  async onHandled(messages) {\n    // some optional logic here for handled messages in batch\n  }\n}\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnAdapter(),\n  outboxOptions: {\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox.start();\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n\n#### Built-in adapters for message handling order\n\n##### SerialAdapter example\n\n`SerialAdapter` can be used for handling messages of batch serially, step by step\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\nimport { SerialAdapter } from 'pg-trx-outbox/dist/src/adapters/abstract/serial'\n\nclass MyOwnAdapter extends SerialAdapter {\n  async start() {\n    // some init logic here, establish connection and so on\n  },\n  \n  async stop() {\n    // some graceful shutdown logic here, close connection and so on\n  },\n  \n  async handleMessage(message) {\n    // serial messages handling here, step by step\n    \n    // example of successful message handling\n    return { value: {someResponse: true}, status: 'fulfilled' },\n    // example of exception while message handling\n    return { reason: 'some error text', status: 'rejected' },\n    // Optional meta property can be returned\n    // which will be saved in appropriate DB column\n    return { value: {someResponse: true}, status: 'fulfilled', meta: {someMeta: true} }\n  },\n\n  async onHandled(messages) {\n    // some optional logic here for handled messages in batch\n  }\n}\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnAdapter(),\n  outboxOptions: {\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox.start();\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n\n##### ParallelAdapter example\n\n`ParallelAdapter` can be used for handling messages in parallel independently of each other\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\nimport { ParallelAdapter } from 'pg-trx-outbox/dist/src/adapters/abstract/parallel'\n\nclass MyOwnAdapter extends ParallelAdapter {\n  async start() {\n    // some init logic here, establish connection and so on\n  },\n  \n  async stop() {\n    // some graceful shutdown logic here, close connection and so on\n  },\n  \n  async handleMessage(message) {\n    // parallel messages handling here\n    \n    // example of successful message handling\n    return { value: {someResponse: true}, status: 'fulfilled' },\n    // example of exception while message handling\n    return { reason: 'some error text', status: 'rejected' },\n    // Optional meta property can be returned\n    // which will be saved in appropriate DB column\n    return { value: {someResponse: true}, status: 'fulfilled', meta: {someMeta: true} }\n  },\n\n  async onHandled(messages) {\n    // some optional logic here for handled messages in batch\n  }\n}\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnAdapter(),\n  outboxOptions: {\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox.start();\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n\n\n##### GroupedAdapter example\n\n`GroupedAdapter` is combination of `SerialAdapter` and `ParallelAdapter`\nMessages with same `key` will be handled serially, step by step. \nBut messages with different `key` will be handled in parallel\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\nimport { GroupedAdapter } from 'pg-trx-outbox/dist/src/adapters/abstract/grouped'\n\nclass MyOwnAdapter extends GroupedAdapter {\n  async start() {\n    // some init logic here, establish connection and so on\n  },\n  \n  async stop() {\n    // some graceful shutdown logic here, close connection and so on\n  },\n  \n  async handleMessage(message) {\n    // grouped by `key` message handling here\n    \n    // example of successful message handling\n    return { value: {someResponse: true}, status: 'fulfilled' },\n    // example of exception while message handling\n    return { reason: 'some error text', status: 'rejected' },\n    // Optional meta property can be returned\n    // which will be saved in appropriate DB column\n    return { value: {someResponse: true}, status: 'fulfilled', meta: {someMeta: true} }\n  },\n\n  async onHandled(messages) {\n    // some optional logic here for handled messages in batch\n  }\n}\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnAdapter(),\n  outboxOptions: {\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox.start();\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n\n##### GroupedAsyncAdapter example\n\n`GroupedAsyncAdapter` is like `GroupedAdapter` but concurrency works globally.\nDon't forget to pass `concurrency: true` option also\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\nimport { GroupedAsyncAdapter } from 'pg-trx-outbox/dist/src/adapters/abstract/grouped'\n\nclass MyOwnAdapter extends GroupedAsyncAdapter {\n  async start() {\n    // some init logic here, establish connection and so on\n  },\n  \n  async stop() {\n    // some graceful shutdown logic here, close connection and so on\n  },\n  \n  async handleMessage(message) {\n    // grouped by `key` message handling here\n    \n    // example of successful message handling\n    return { value: {someResponse: true}, status: 'fulfilled' },\n    // example of exception while message handling\n    return { reason: 'some error text', status: 'rejected' },\n    // Optional meta property can be returned\n    // which will be saved in appropriate DB column\n    return { value: {someResponse: true}, status: 'fulfilled', meta: {someMeta: true} }\n  },\n\n  async onHandled(messages) {\n    // some optional logic here for handled messages in batch\n  }\n}\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnAdapter(),\n  outboxOptions: {\n    concurrency: true,\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox.start();\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n\n## Partitioning\n\nOver time, performance of one table `pg_trx_outbox` can be not enough.\nAlso, handling messages throughput can be not enough too.\nIn this cases native PostgreSQL partitioning can help and **pg-trx-outbox** supports it.\nImplementation inspired by Kafka key partitioning mechanism.\n\n#### Partitioning example\n\n##### SQL migration\n\n```sql\nCREATE TABLE pg_trx_outbox (\n  id bigserial NOT NULL,\n  processed bool NOT NULL DEFAULT false,\n  created_at timestamptz NOT NULL DEFAULT now(),\n  updated_at timestamptz NOT NULL DEFAULT now(),\n  since_at timestamptz,\n  topic text NOT NULL,\n  \"key\" text NULL,\n  value jsonb NULL,\n  \"partition\" int2 NULL,\n  \"timestamp\" int8 NULL,\n  headers jsonb NULL,\n  response jsonb NULL,\n  error text NULL,\n  meta jsonb NULL,\n  context_id double precision NOT NULL DEFAULT random(),\n  attempts smallint NOT NULL DEFAULT 0,\n  is_event bool NOT NULL DEFAULT false,\n  error_approved boolean NOT NULL DEFAULT false,\n  CONSTRAINT pg_trx_outbox_pk PRIMARY KEY (id, key)\n) PARTITION BY HASH (key);\n\nCREATE TABLE pg_trx_outbox_0 PARTITION OF pg_trx_outbox FOR VALUES WITH (MODULUS 3, REMAINDER 0);\nCREATE TABLE pg_trx_outbox_1 PARTITION OF pg_trx_outbox FOR VALUES WITH (MODULUS 3, REMAINDER 1);\nCREATE TABLE pg_trx_outbox_2 PARTITION OF pg_trx_outbox FOR VALUES WITH (MODULUS 3, REMAINDER 2);\n```\n\n##### Code\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\n\nconst pgTrxOutbox0 = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnOrBuiltInAdapter(),\n  outboxOptions: {\n    partition: 0 // this istance of `PgTrxOutbox` will handle only `0` partition\n    /* [2] */\n  }\n});\nconst pgTrxOutbox1 = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnOrBuiltInAdapter(),\n  outboxOptions: {\n    partition: 1 // this instance of `PgTrxOutbox` will handle only `1` partition\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox0.start();\nawait pgTrxOutbox1.start();\n\n// on shutdown\n\nawait pgTrxOutbox0.stop();\nawait pgTrxOutbox1.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n\n## Built-in messages meta\n\nIn *Custom adapter* section you already see, that `meta` field can be returned in message handling\nand implicitly will be saved in DB in appropriate column. Also **pg-trx-outbox** provide some built-in `meta`,\nwhich will be persisted with using of `SerialAdapter`, `ParallelAdapter` or `GroupedAdapter`:\n\n```js\n{\n  pgTrxOutbox: {\n    time: 1.343434, // time of message handling execution, high-precision count of milliseconds\n    libuv: { /* [1] */ }, // contains libuv metrics for detecting CPU stucks\n    beforeMemory: { /* [2] */ }, // memory usage before event\n    afterMemory: { /* [2] */ }, // memory usage after event\n    uptime: 100, // [3] uptime of process\n    cpuUsage: { /* [4] */ } // CPU usage per event\n  }\n}\n```\n\n- [1] https://nodejs.org/dist/latest-v20.x/docs/api/perf_hooks.html#class-histogram\n- [2] https://nodejs.org/dist/latest-v20.x/docs/api/process.html#processmemoryusage\n- [3] https://nodejs.org/dist/latest-v20.x/docs/api/process.html#processuptime\n- [4] https://nodejs.org/dist/latest-v20.x/docs/api/process.html#processcpuusagepreviousvalue\n\n## Context id\n\nVery useful to link chain of messages handling with through `contextId`\u003cbr/\u003e\nMore info about pattern: https://microservices.io/patterns/observability/distributed-tracing.html \u003cbr/\u003e\nUsing `SerialAdapter`, `ParallelAdapter` or `GroupedAdapter` very easy to extract `contextId` of current handled message and pass it futher:\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\nimport { SerialAdapter } from 'pg-trx-outbox/dist/src/adapters/abstract/serial'\n\nclass MyOwnAdapter extends SerialAdapter {\n  async start() {\n    // some init logic here, establish connection and so on\n  },\n  \n  async stop() {\n    // some graceful shutdown logic here, close connection and so on\n  },\n  \n  async handleMessage(message) {\n    // `contextId` can be extracted in any place of async stack and passed to futher\n    // AsyncLocalStorage will used for this purpose [3]\n    // `pg` is hypothetical object of your code, which means PostgreSQL client\n    await pg.query(`\n      insert into pg_trx_outbox (topic, \"key\", value, context_id)\n      values ('some.topic', 'key', '{\"someValue\": true}', $1)\n    `, [pgTrxOutbox.contextId()])\n  },\n\n  async onHandled(messages) {\n    // some optional logic here for handled messages in batch\n  }\n}\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnAdapter(),\n  outboxOptions: {\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox.start();\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n- [3] https://nodejs.org/dist/latest-v20.x/docs/api/async_context.html#class-asynclocalstorage\n\n## Filter messages handling by topic\n\nMessages handling can be filtered by topic. \nIt's useful, when you have multiple microservices with one shared DB \nand each microservice should handle specific bunch of messages filtered by topic.\n\n##### More optimized index for not processed messages\n\n```sql\nCREATE INDEX pg_trx_outbox_not_processed_idx\n  ON pg_trx_outbox (processed, topic, id)\n  WHERE (processed = false);\n```\n\n##### Topics filter example\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnOrBuiltInAdapter(),\n  outboxOptions: {\n    topicFilter: ['for.handle'] // Array of topics, which should be handled only\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox.start();\n\nawait pg\n  .query\u003c{ id: string }\u003e(\n    `\n      INSERT INTO pg_trx_outbox (topic, \"key\", value)\n      VALUES ('for.handle', 'someKey', '{\"someValue\": true}'), -- this message will be handled\n        ('not.for.handle', 'someKey', '{\"someValue\": true}') -- this message not will be handled\n    `\n  )\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n\n## Scheduling of messages\n\nMessages can be delayed and handled at some time in future\n\n##### Messages scheduling example\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnOrBuiltInAdapter(),\n  outboxOptions: {\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox.start();\n\nawait pg\n  .query\u003c{ id: string }\u003e(\n    `\n      INSERT INTO pg_trx_outbox (topic, \"key\", value, since_at)\n      VALUES ('for.handle', 'someKey', '{\"someValue\": true}', now() + interval '1 hour'), -- this message will be handled since 1 hour\n        ('not.for.handle', 'someKey', '{\"someValue\": true}', null), -- this message not will be handled ASAP\n        ('not.for.handle', 'someKey', '{\"someValue\": true}', now() - interval '1 hour'), -- this message not will be handled ASAP\n    `\n  )\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n\n## Retrying of errors\n\nMessages can be retried via predicate\n\n##### Retrying of errors example\n\n```ts\nimport { PgTrxOutbox } from 'pg-trx-outbox'\n\nconst pgTrxOutbox = new PgTrxOutbox({\n  pgOptions: {/* [1] */},\n  adapter: new MyOwnOrBuiltInAdapter(),\n  outboxOptions: {\n    retryError(err) {\n      // return true for retrying\n    },\n    retryDelay: 5, // delay of retry in seconds, default is 5\n    retryMaxAttempts: 5 // max attempts for retry, default is 5\n    /* [2] */\n  }\n});\n\nawait pgTrxOutbox.start();\n\nawait pg\n  .query\u003c{ id: string }\u003e(\n    `\n      INSERT INTO pg_trx_outbox (topic, \"key\", value)\n      VALUES ('for.handle', 'someKey', '{\"someValue\": true}')\n    `\n  )\n\n// on shutdown\n\nawait pgTrxOutbox.stop();\n```\n\n- [1] https://node-postgres.com/apis/pool\n- [2] https://github.com/darky/pg-trx-outbox/blob/master/src/types.ts#L42\n\n## Compressed events\n\nEvents can be compresed with particular batch size for speed up of initial events consuming\n\n* $1 - array of filter by topic\n* $2 - chunk size (e.g. 50 or 100 or ...)\n\n```sql\nwith with_pre_batch_ids as (\n  select\n    id, topic, value,\n    case when lag(topic) over(order by id asc) is null or lag(topic) over(order by id asc) = topic\n      then 0\n      else 1\n    end pre_batch_id\n  from pg_trx_outbox\n  where is_event and topic = any($1)\n  order by id asc\n), with_batch_ids as (\n  select\n    id, topic, value,\n    sum(pre_batch_id) over (order by id asc) batch_id\n  from with_pre_batch_ids\n), with_chunk_ids as (\n  select\n    id, topic, value, batch_id,\n    ceil((row_number() over(partition by topic, batch_id order by id asc) - 1) / $2) chunk_id\n  from with_batch_ids\n  order by id asc\n)\nselect\n  (array_agg(id))[1] min_id,\n  topic,\n  jsonb_agg(value) value\nfrom with_chunk_ids\ngroup by batch_id, chunk_id, topic\n```\n\n## Auto approving of errors\n\nError can be approved automatically (`error_approved = true`). `error_approved` flag intended to distinguish business logic errors from unexpected. Just throw `ApprovedError` 👇 for your expected business logic errors.\n\n```typescript\nclass ApprovedError extends Error {\n  isApproved = true\n}\n```\n\n## Debugging\n\nSet the `DEBUG` environment variable to `pg-trx-outbox:*` to enable debug logging.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarky%2Fpg-trx-outbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdarky%2Fpg-trx-outbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarky%2Fpg-trx-outbox/lists"}