@outbox-event-bus/postgres-drizzle-outbox 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -194,13 +194,13 @@ async function createUser(user: any) {
194
194
  return await db.transaction(async (tx) => {
195
195
  return await als.run(tx, async () => {
196
196
  await tx.insert(users).values(user);
197
-
197
+
198
198
  // Bus automatically uses transaction from ALS
199
- await bus.emit({
200
- type: 'user.created',
201
- payload: user
199
+ await bus.emit({
200
+ type: 'user.created',
201
+ payload: user
202
202
  });
203
-
203
+
204
204
  return user;
205
205
  });
206
206
  });
@@ -210,6 +210,40 @@ async function createUser(user: any) {
210
210
  > [!TIP]
211
211
  > AsyncLocalStorage eliminates the need to pass transactions through your call stack, improving code clarity.
212
212
 
213
+ ### 4. Typed Schema Support
214
+
215
+ `PostgresDrizzleOutbox` accepts a generic schema type parameter, so you can pass your typed Drizzle database instance directly without casting:
216
+
217
+ ```typescript
218
+ import { drizzle } from 'drizzle-orm/postgres-js';
219
+ import postgres from 'postgres';
220
+ import * as schema from './schema';
221
+ import { PostgresDrizzleOutbox } from '@outbox-event-bus/postgres-drizzle-outbox';
222
+
223
+ const client = postgres(process.env.DATABASE_URL!);
224
+ const db = drizzle(client, { schema });
225
+
226
+ // Schema type is inferred — no need to cast db
227
+ const outbox = new PostgresDrizzleOutbox({
228
+ db,
229
+ getTransaction: () => transactionManager.getCurrentTransaction()
230
+ });
231
+ ```
232
+
233
+ The `createDrizzleTransactionStorage` helper also supports typed schemas:
234
+
235
+ ```typescript
236
+ import { createDrizzleTransactionStorage } from '@outbox-event-bus/postgres-drizzle-outbox';
237
+ import * as schema from './schema';
238
+
239
+ const { withTransaction, getTransaction } = createDrizzleTransactionStorage<typeof schema>();
240
+
241
+ const outbox = new PostgresDrizzleOutbox({
242
+ db,
243
+ getTransaction
244
+ });
245
+ ```
246
+
213
247
  ## Custom Table Schemas
214
248
 
215
249
  The adapter supports custom table definitions, enabling:
package/dist/index.cjs CHANGED
@@ -103,18 +103,22 @@ var PostgresDrizzleOutbox = class {
103
103
  await this.poller.stop();
104
104
  }
105
105
  async processBatch(handler) {
106
- await this.config.db.transaction(async (transaction) => {
107
- const now = /* @__PURE__ */ new Date();
106
+ const now = /* @__PURE__ */ new Date();
107
+ const lockedEvents = await this.config.db.transaction(async (transaction) => {
108
108
  const events = await transaction.select().from(this.config.tables.outboxEvents).where((0, drizzle_orm.or)((0, drizzle_orm.eq)(this.config.tables.outboxEvents.status, outbox_event_bus.EventStatus.CREATED), (0, drizzle_orm.and)((0, drizzle_orm.eq)(this.config.tables.outboxEvents.status, outbox_event_bus.EventStatus.FAILED), (0, drizzle_orm.lt)(this.config.tables.outboxEvents.retryCount, this.config.maxRetries), (0, drizzle_orm.lt)(this.config.tables.outboxEvents.nextRetryAt, now)), (0, drizzle_orm.and)((0, drizzle_orm.eq)(this.config.tables.outboxEvents.status, outbox_event_bus.EventStatus.ACTIVE), (0, drizzle_orm.lt)(this.config.tables.outboxEvents.keepAlive, drizzle_orm.sql`${now.toISOString()}::timestamp - make_interval(secs => ${this.config.tables.outboxEvents.expireInSeconds})`)))).limit(this.config.batchSize).for("update", { skipLocked: true });
109
- if (events.length === 0) return;
109
+ if (events.length === 0) return [];
110
110
  const eventIds = events.map((event) => event.id);
111
111
  await transaction.update(this.config.tables.outboxEvents).set({
112
112
  status: outbox_event_bus.EventStatus.ACTIVE,
113
113
  startedOn: now,
114
114
  keepAlive: now
115
115
  }).where((0, drizzle_orm.inArray)(this.config.tables.outboxEvents.id, eventIds));
116
- for (const event of events) try {
117
- await handler(event);
116
+ return events;
117
+ });
118
+ if (lockedEvents.length === 0) return;
119
+ for (const event of lockedEvents) try {
120
+ await handler(event);
121
+ await this.config.db.transaction(async (transaction) => {
118
122
  await transaction.insert(this.config.tables.outboxEventsArchive).values({
119
123
  id: event.id,
120
124
  type: event.type,
@@ -127,36 +131,65 @@ var PostgresDrizzleOutbox = class {
127
131
  completedOn: /* @__PURE__ */ new Date()
128
132
  });
129
133
  await transaction.delete(this.config.tables.outboxEvents).where((0, drizzle_orm.eq)(this.config.tables.outboxEvents.id, event.id));
130
- } catch (error) {
131
- const retryCount = event.retryCount + 1;
132
- (0, outbox_event_bus.reportEventError)(this.poller.onError, error, event, retryCount, this.config.maxRetries);
133
- const delay = this.poller.calculateBackoff(retryCount);
134
- await transaction.update(this.config.tables.outboxEvents).set({
135
- status: outbox_event_bus.EventStatus.FAILED,
136
- retryCount,
137
- lastError: (0, outbox_event_bus.formatErrorMessage)(error),
138
- nextRetryAt: new Date(Date.now() + delay)
139
- }).where((0, drizzle_orm.eq)(this.config.tables.outboxEvents.id, event.id));
140
- }
141
- });
134
+ });
135
+ } catch (error) {
136
+ const retryCount = event.retryCount + 1;
137
+ (0, outbox_event_bus.reportEventError)(this.poller.onError, error, event, retryCount, this.config.maxRetries);
138
+ const delay = this.poller.calculateBackoff(retryCount);
139
+ await this.config.db.update(this.config.tables.outboxEvents).set({
140
+ status: outbox_event_bus.EventStatus.FAILED,
141
+ retryCount,
142
+ lastError: (0, outbox_event_bus.formatErrorMessage)(error),
143
+ nextRetryAt: new Date(Date.now() + delay)
144
+ }).where((0, drizzle_orm.eq)(this.config.tables.outboxEvents.id, event.id));
145
+ }
142
146
  }
143
147
  };
144
148
 
145
149
  //#endregion
146
150
  //#region src/transaction-storage.ts
151
+ function createDrizzleTransactionStorage() {
152
+ const storage = new node_async_hooks.AsyncLocalStorage();
153
+ async function withTransaction(db, fn) {
154
+ return db.transaction(async (tx) => {
155
+ return storage.run(tx, () => fn(tx));
156
+ });
157
+ }
158
+ function getTransaction() {
159
+ return storage.getStore();
160
+ }
161
+ return {
162
+ storage,
163
+ withTransaction,
164
+ getTransaction
165
+ };
166
+ }
167
+ /**
168
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
169
+ */
147
170
  const drizzleTransactionStorage = new node_async_hooks.AsyncLocalStorage();
171
+ /**
172
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
173
+ */
148
174
  async function withDrizzleTransaction(db, fn) {
149
175
  return db.transaction(async (tx) => {
150
176
  return drizzleTransactionStorage.run(tx, () => fn(tx));
151
177
  });
152
178
  }
179
+ /**
180
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
181
+ */
153
182
  function getDrizzleTransaction() {
154
183
  return () => drizzleTransactionStorage.getStore();
155
184
  }
156
185
 
157
186
  //#endregion
158
187
  exports.PostgresDrizzleOutbox = PostgresDrizzleOutbox;
188
+ exports.createDrizzleTransactionStorage = createDrizzleTransactionStorage;
159
189
  exports.drizzleTransactionStorage = drizzleTransactionStorage;
160
190
  exports.getDrizzleTransaction = getDrizzleTransaction;
191
+ exports.outboxEvents = outboxEvents;
192
+ exports.outboxEventsArchive = outboxEventsArchive;
193
+ exports.outboxStatusEnum = outboxStatusEnum;
161
194
  exports.withDrizzleTransaction = withDrizzleTransaction;
162
195
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["PollingService","EventStatus","failedEvent: FailedBusEvent","error: unknown","AsyncLocalStorage"],"sources":["../src/schema.ts","../src/postgres-drizzle-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":["import { integer, jsonb, pgEnum, pgTable, text, timestamp, uuid } from \"drizzle-orm/pg-core\"\n\nexport const outboxStatusEnum = pgEnum(\"outbox_status\", [\n \"created\",\n \"active\",\n \"completed\",\n \"failed\",\n])\n\nexport const outboxEvents = pgTable(\"outbox_events\", {\n id: uuid(\"id\").primaryKey(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n occurredAt: timestamp(\"occurred_at\").notNull(),\n status: outboxStatusEnum(\"status\").notNull().default(\"created\"),\n retryCount: integer(\"retry_count\").notNull().default(0),\n lastError: text(\"last_error\"),\n nextRetryAt: timestamp(\"next_retry_at\"),\n createdOn: timestamp(\"created_on\").notNull().defaultNow(),\n startedOn: timestamp(\"started_on\"),\n completedOn: timestamp(\"completed_on\"),\n keepAlive: timestamp(\"keep_alive\"),\n expireInSeconds: integer(\"expire_in_seconds\").notNull().default(300),\n})\n\nexport const outboxEventsArchive = pgTable(\"outbox_events_archive\", {\n id: uuid(\"id\").primaryKey(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n occurredAt: timestamp(\"occurred_at\").notNull(),\n status: outboxStatusEnum(\"status\").notNull(),\n retryCount: integer(\"retry_count\").notNull(),\n lastError: text(\"last_error\"),\n createdOn: timestamp(\"created_on\").notNull(),\n startedOn: timestamp(\"started_on\"),\n completedOn: timestamp(\"completed_on\").notNull(),\n})\n","import { and, eq, inArray, lt, or, sql } from \"drizzle-orm\"\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\"\nimport {\n type BusEvent,\n type ErrorHandler,\n EventStatus,\n type FailedBusEvent,\n formatErrorMessage,\n type IOutbox,\n type OutboxConfig,\n PollingService,\n reportEventError,\n} from \"outbox-event-bus\"\nimport { outboxEvents, outboxEventsArchive } from \"./schema\"\n\nexport interface PostgresDrizzleOutboxConfig extends OutboxConfig {\n db: PostgresJsDatabase<Record<string, unknown>>\n getTransaction?: (() => PostgresJsDatabase<Record<string, unknown>> | undefined) | undefined\n tables?: {\n outboxEvents: typeof outboxEvents\n outboxEventsArchive: typeof outboxEventsArchive\n }\n}\n\nexport class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<string, unknown>>> {\n private readonly config: Required<PostgresDrizzleOutboxConfig>\n private readonly poller: PollingService\n\n constructor(config: PostgresDrizzleOutboxConfig) {\n this.config = {\n batchSize: config.batchSize ?? 50,\n pollIntervalMs: config.pollIntervalMs ?? 1000,\n maxRetries: config.maxRetries ?? 5,\n baseBackoffMs: config.baseBackoffMs ?? 1000,\n processingTimeoutMs: config.processingTimeoutMs ?? 30000,\n maxErrorBackoffMs: config.maxErrorBackoffMs ?? 30000,\n db: config.db,\n getTransaction: config.getTransaction,\n tables: config.tables ?? {\n outboxEvents,\n outboxEventsArchive,\n },\n }\n\n this.poller = new PollingService({\n pollIntervalMs: this.config.pollIntervalMs,\n baseBackoffMs: this.config.baseBackoffMs,\n maxErrorBackoffMs: this.config.maxErrorBackoffMs,\n processBatch: (handler) => this.processBatch(handler),\n })\n }\n\n async publish(\n events: BusEvent[],\n transaction?: PostgresJsDatabase<Record<string, unknown>>\n ): Promise<void> {\n const executor = transaction ?? this.config.getTransaction?.() ?? this.config.db\n\n await executor.insert(this.config.tables.outboxEvents).values(\n events.map((event) => ({\n id: event.id,\n type: event.type,\n payload: event.payload,\n occurredAt: event.occurredAt,\n status: EventStatus.CREATED,\n }))\n )\n }\n\n async getFailedEvents(): Promise<FailedBusEvent[]> {\n const events = await this.config.db\n .select()\n .from(this.config.tables.outboxEvents)\n .where(eq(this.config.tables.outboxEvents.status, EventStatus.FAILED))\n .orderBy(sql`${this.config.tables.outboxEvents.occurredAt} DESC`)\n .limit(100)\n\n return events.map((event) => {\n const failedEvent: FailedBusEvent = {\n id: event.id,\n type: event.type,\n payload: event.payload as any,\n occurredAt: event.occurredAt,\n retryCount: event.retryCount,\n }\n if (event.lastError) failedEvent.error = event.lastError\n if (event.startedOn) failedEvent.lastAttemptAt = event.startedOn\n return failedEvent\n })\n }\n\n async retryEvents(eventIds: string[]): Promise<void> {\n await this.config.db\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.CREATED,\n retryCount: 0,\n nextRetryAt: null,\n lastError: null,\n })\n .where(inArray(this.config.tables.outboxEvents.id, eventIds))\n }\n\n start(handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler): void {\n this.poller.start(handler, onError)\n }\n\n async stop(): Promise<void> {\n await this.poller.stop()\n }\n\n private async processBatch(handler: (event: BusEvent) => Promise<void>) {\n await this.config.db.transaction(async (transaction) => {\n const now = new Date()\n\n // Select events that are:\n // 1. New (status = created)\n // 2. Failed but can be retried (retry count < max AND retry time has passed)\n // 3. Active but stuck/timed out (keepAlive is older than now - expireInSeconds)\n const events = await transaction\n .select()\n .from(this.config.tables.outboxEvents)\n .where(\n or(\n eq(this.config.tables.outboxEvents.status, EventStatus.CREATED),\n and(\n eq(this.config.tables.outboxEvents.status, EventStatus.FAILED),\n lt(this.config.tables.outboxEvents.retryCount, this.config.maxRetries),\n lt(this.config.tables.outboxEvents.nextRetryAt, now)\n ),\n and(\n eq(this.config.tables.outboxEvents.status, EventStatus.ACTIVE),\n // Check if event is stuck: keepAlive is older than (now - expireInSeconds)\n // This uses PostgreSQL's make_interval to subtract expireInSeconds from current timestamp\n lt(\n this.config.tables.outboxEvents.keepAlive,\n sql`${now.toISOString()}::timestamp - make_interval(secs => ${this.config.tables.outboxEvents.expireInSeconds})`\n )\n )\n )\n )\n .limit(this.config.batchSize)\n .for(\"update\", { skipLocked: true })\n\n if (events.length === 0) return\n\n const eventIds = events.map((event) => event.id)\n\n await transaction\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.ACTIVE,\n startedOn: now,\n keepAlive: now,\n })\n .where(inArray(this.config.tables.outboxEvents.id, eventIds))\n\n for (const event of events) {\n try {\n await handler(event)\n // Archive successful event immediately\n await transaction.insert(this.config.tables.outboxEventsArchive).values({\n id: event.id,\n type: event.type,\n payload: event.payload,\n occurredAt: event.occurredAt,\n status: EventStatus.COMPLETED,\n retryCount: event.retryCount,\n createdOn: event.createdOn,\n startedOn: now,\n completedOn: new Date(),\n })\n await transaction\n .delete(this.config.tables.outboxEvents)\n .where(eq(this.config.tables.outboxEvents.id, event.id))\n } catch (error: unknown) {\n const retryCount = event.retryCount + 1\n reportEventError(this.poller.onError, error, event, retryCount, this.config.maxRetries)\n\n // Mark this specific event as failed\n const delay = this.poller.calculateBackoff(retryCount)\n await transaction\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.FAILED,\n retryCount,\n lastError: formatErrorMessage(error),\n nextRetryAt: new Date(Date.now() + delay),\n })\n .where(eq(this.config.tables.outboxEvents.id, event.id))\n }\n }\n })\n }\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\"\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\"\n\nexport const drizzleTransactionStorage = new AsyncLocalStorage<\n PostgresJsDatabase<Record<string, unknown>>\n>()\n\nexport async function withDrizzleTransaction<T>(\n db: PostgresJsDatabase<Record<string, unknown>>,\n fn: (tx: PostgresJsDatabase<Record<string, unknown>>) => Promise<T>\n): Promise<T> {\n return db.transaction(async (tx) => {\n return drizzleTransactionStorage.run(tx, () => fn(tx))\n })\n}\n\nexport function getDrizzleTransaction(): () =>\n | PostgresJsDatabase<Record<string, unknown>>\n | undefined {\n return () => drizzleTransactionStorage.getStore()\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["PollingService","EventStatus","failedEvent: FailedBusEvent","error: unknown","AsyncLocalStorage"],"sources":["../src/schema.ts","../src/postgres-drizzle-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":["import {\n integer,\n jsonb,\n pgEnum,\n pgTable,\n text,\n timestamp,\n uuid,\n} from \"drizzle-orm/pg-core\"\n\n\nexport const outboxStatusEnum = pgEnum(\"outbox_status\", [\n \"created\",\n \"active\",\n \"completed\",\n \"failed\",\n])\n\nexport const outboxEvents = pgTable(\"outbox_events\", {\n id: uuid(\"id\").primaryKey(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n occurredAt: timestamp(\"occurred_at\").notNull(),\n status: outboxStatusEnum(\"status\").notNull().default(\"created\"),\n retryCount: integer(\"retry_count\").notNull().default(0),\n lastError: text(\"last_error\"),\n nextRetryAt: timestamp(\"next_retry_at\"),\n createdOn: timestamp(\"created_on\").notNull().defaultNow(),\n startedOn: timestamp(\"started_on\"),\n completedOn: timestamp(\"completed_on\"),\n keepAlive: timestamp(\"keep_alive\"),\n expireInSeconds: integer(\"expire_in_seconds\").notNull().default(300),\n})\n\nexport const outboxEventsArchive = pgTable(\"outbox_events_archive\", {\n id: uuid(\"id\").primaryKey(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n occurredAt: timestamp(\"occurred_at\").notNull(),\n status: outboxStatusEnum(\"status\").notNull(),\n retryCount: integer(\"retry_count\").notNull(),\n lastError: text(\"last_error\"),\n createdOn: timestamp(\"created_on\").notNull(),\n startedOn: timestamp(\"started_on\"),\n completedOn: timestamp(\"completed_on\").notNull(),\n})\n","import { and, eq, inArray, lt, or, sql } from \"drizzle-orm\"\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\"\nimport {\n type BusEvent,\n type ErrorHandler,\n EventStatus,\n type FailedBusEvent,\n formatErrorMessage,\n type IOutbox,\n type OutboxConfig,\n PollingService,\n reportEventError,\n} from \"outbox-event-bus\"\nimport { outboxEvents, outboxEventsArchive } from \"./schema\"\n\nexport interface PostgresDrizzleOutboxConfig<TSchema extends Record<string, unknown> = Record<string, unknown>> extends OutboxConfig {\n db: PostgresJsDatabase<TSchema>\n getTransaction?: (() => PostgresJsDatabase<TSchema> | undefined) | undefined\n tables?: {\n outboxEvents: typeof outboxEvents\n outboxEventsArchive: typeof outboxEventsArchive\n }\n}\n\nexport class PostgresDrizzleOutbox<TSchema extends Record<string, unknown> = Record<string, unknown>> implements IOutbox<PostgresJsDatabase<TSchema>> {\n private readonly config: Required<PostgresDrizzleOutboxConfig<TSchema>>\n private readonly poller: PollingService\n\n constructor(config: PostgresDrizzleOutboxConfig<TSchema>) {\n this.config = {\n batchSize: config.batchSize ?? 50,\n pollIntervalMs: config.pollIntervalMs ?? 1000,\n maxRetries: config.maxRetries ?? 5,\n baseBackoffMs: config.baseBackoffMs ?? 1000,\n processingTimeoutMs: config.processingTimeoutMs ?? 30000,\n maxErrorBackoffMs: config.maxErrorBackoffMs ?? 30000,\n db: config.db,\n getTransaction: config.getTransaction,\n tables: config.tables ?? {\n outboxEvents,\n outboxEventsArchive,\n },\n }\n\n this.poller = new PollingService({\n pollIntervalMs: this.config.pollIntervalMs,\n baseBackoffMs: this.config.baseBackoffMs,\n maxErrorBackoffMs: this.config.maxErrorBackoffMs,\n processBatch: (handler) => this.processBatch(handler),\n })\n }\n\n async publish(\n events: BusEvent[],\n transaction?: PostgresJsDatabase<TSchema>\n ): Promise<void> {\n const executor = transaction ?? this.config.getTransaction?.() ?? this.config.db\n\n await executor.insert(this.config.tables.outboxEvents).values(\n events.map((event) => ({\n id: event.id,\n type: event.type,\n payload: event.payload,\n occurredAt: event.occurredAt,\n status: EventStatus.CREATED,\n }))\n )\n }\n\n async getFailedEvents(): Promise<FailedBusEvent[]> {\n const events = await this.config.db\n .select()\n .from(this.config.tables.outboxEvents)\n .where(eq(this.config.tables.outboxEvents.status, EventStatus.FAILED))\n .orderBy(sql`${this.config.tables.outboxEvents.occurredAt} DESC`)\n .limit(100)\n\n return events.map((event) => {\n const failedEvent: FailedBusEvent = {\n id: event.id,\n type: event.type,\n payload: event.payload as any,\n occurredAt: event.occurredAt,\n retryCount: event.retryCount,\n }\n if (event.lastError) failedEvent.error = event.lastError\n if (event.startedOn) failedEvent.lastAttemptAt = event.startedOn\n return failedEvent\n })\n }\n\n async retryEvents(eventIds: string[]): Promise<void> {\n await this.config.db\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.CREATED,\n retryCount: 0,\n nextRetryAt: null,\n lastError: null,\n })\n .where(inArray(this.config.tables.outboxEvents.id, eventIds))\n }\n\n start(handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler): void {\n this.poller.start(handler, onError)\n }\n\n async stop(): Promise<void> {\n await this.poller.stop()\n }\n\n private async processBatch(handler: (event: BusEvent) => Promise<void>) {\n const now = new Date()\n\n const lockedEvents = await this.config.db.transaction(async (transaction) => {\n const events = await transaction\n .select()\n .from(this.config.tables.outboxEvents)\n .where(\n or(\n eq(this.config.tables.outboxEvents.status, EventStatus.CREATED),\n and(\n eq(this.config.tables.outboxEvents.status, EventStatus.FAILED),\n lt(this.config.tables.outboxEvents.retryCount, this.config.maxRetries),\n lt(this.config.tables.outboxEvents.nextRetryAt, now)\n ),\n and(\n eq(this.config.tables.outboxEvents.status, EventStatus.ACTIVE),\n lt(\n this.config.tables.outboxEvents.keepAlive,\n sql`${now.toISOString()}::timestamp - make_interval(secs => ${this.config.tables.outboxEvents.expireInSeconds})`\n )\n )\n )\n )\n .limit(this.config.batchSize)\n .for(\"update\", { skipLocked: true })\n\n if (events.length === 0) return []\n\n const eventIds = events.map((event) => event.id)\n\n await transaction\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.ACTIVE,\n startedOn: now,\n keepAlive: now,\n })\n .where(inArray(this.config.tables.outboxEvents.id, eventIds))\n\n return events\n })\n\n if (lockedEvents.length === 0) return\n\n // 2. Process events outside of the transaction\n for (const event of lockedEvents) {\n try {\n await handler(event)\n\n // use the main db connection for individual updates to avoid long transactions\n await this.config.db.transaction(async (transaction) => {\n await transaction.insert(this.config.tables.outboxEventsArchive).values({\n id: event.id,\n type: event.type,\n payload: event.payload as any,\n occurredAt: event.occurredAt,\n status: EventStatus.COMPLETED,\n retryCount: event.retryCount,\n createdOn: event.createdOn,\n startedOn: now,\n completedOn: new Date(),\n })\n await transaction\n .delete(this.config.tables.outboxEvents)\n .where(eq(this.config.tables.outboxEvents.id, event.id))\n })\n } catch (error: unknown) {\n const retryCount = event.retryCount + 1\n reportEventError(this.poller.onError, error, event, retryCount, this.config.maxRetries)\n\n const delay = this.poller.calculateBackoff(retryCount)\n await this.config.db\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.FAILED,\n retryCount,\n lastError: formatErrorMessage(error),\n nextRetryAt: new Date(Date.now() + delay),\n })\n .where(eq(this.config.tables.outboxEvents.id, event.id))\n }\n }\n }\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\"\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\"\n\nexport function createDrizzleTransactionStorage<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n>() {\n const storage = new AsyncLocalStorage<PostgresJsDatabase<TSchema>>()\n\n async function withTransaction<T>(\n db: PostgresJsDatabase<TSchema>,\n fn: (tx: PostgresJsDatabase<TSchema>) => Promise<T>\n ): Promise<T> {\n return db.transaction(async (tx) => {\n return storage.run(tx, () => fn(tx))\n })\n }\n\n function getTransaction(): PostgresJsDatabase<TSchema> | undefined {\n return storage.getStore()\n }\n\n return { storage, withTransaction, getTransaction }\n}\n\n/**\n * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.\n */\nexport const drizzleTransactionStorage = new AsyncLocalStorage<\n PostgresJsDatabase<Record<string, unknown>>\n>()\n\n/**\n * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.\n */\nexport async function withDrizzleTransaction<T>(\n db: PostgresJsDatabase<Record<string, unknown>>,\n fn: (tx: PostgresJsDatabase<Record<string, unknown>>) => Promise<T>\n): Promise<T> {\n return db.transaction(async (tx) => {\n return drizzleTransactionStorage.run(tx, () => fn(tx))\n })\n}\n\n/**\n * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.\n */\nexport function getDrizzleTransaction(): () =>\n | PostgresJsDatabase<Record<string, unknown>>\n | undefined {\n return () => drizzleTransactionStorage.getStore()\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -46,6 +46,7 @@ interface OutboxConfig {
46
46
  }
47
47
  //#endregion
48
48
  //#region src/schema.d.ts
49
+ declare const outboxStatusEnum: drizzle_orm_pg_core0.PgEnum<["created", "active", "completed", "failed"]>;
49
50
  declare const outboxEvents: drizzle_orm_pg_core0.PgTableWithColumns<{
50
51
  name: "outbox_events";
51
52
  schema: undefined;
@@ -453,19 +454,19 @@ declare const outboxEventsArchive: drizzle_orm_pg_core0.PgTableWithColumns<{
453
454
  }>;
454
455
  //#endregion
455
456
  //#region src/postgres-drizzle-outbox.d.ts
456
- interface PostgresDrizzleOutboxConfig extends OutboxConfig {
457
- db: PostgresJsDatabase<Record<string, unknown>>;
458
- getTransaction?: (() => PostgresJsDatabase<Record<string, unknown>> | undefined) | undefined;
457
+ interface PostgresDrizzleOutboxConfig<TSchema extends Record<string, unknown> = Record<string, unknown>> extends OutboxConfig {
458
+ db: PostgresJsDatabase<TSchema>;
459
+ getTransaction?: (() => PostgresJsDatabase<TSchema> | undefined) | undefined;
459
460
  tables?: {
460
461
  outboxEvents: typeof outboxEvents;
461
462
  outboxEventsArchive: typeof outboxEventsArchive;
462
463
  };
463
464
  }
464
- declare class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<string, unknown>>> {
465
+ declare class PostgresDrizzleOutbox<TSchema extends Record<string, unknown> = Record<string, unknown>> implements IOutbox<PostgresJsDatabase<TSchema>> {
465
466
  private readonly config;
466
467
  private readonly poller;
467
- constructor(config: PostgresDrizzleOutboxConfig);
468
- publish(events: BusEvent[], transaction?: PostgresJsDatabase<Record<string, unknown>>): Promise<void>;
468
+ constructor(config: PostgresDrizzleOutboxConfig<TSchema>);
469
+ publish(events: BusEvent[], transaction?: PostgresJsDatabase<TSchema>): Promise<void>;
469
470
  getFailedEvents(): Promise<FailedBusEvent[]>;
470
471
  retryEvents(eventIds: string[]): Promise<void>;
471
472
  start(handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler): void;
@@ -474,9 +475,23 @@ declare class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record
474
475
  }
475
476
  //#endregion
476
477
  //#region src/transaction-storage.d.ts
478
+ declare function createDrizzleTransactionStorage<TSchema extends Record<string, unknown> = Record<string, unknown>>(): {
479
+ storage: AsyncLocalStorage<PostgresJsDatabase<TSchema>>;
480
+ withTransaction: <T>(db: PostgresJsDatabase<TSchema>, fn: (tx: PostgresJsDatabase<TSchema>) => Promise<T>) => Promise<T>;
481
+ getTransaction: () => PostgresJsDatabase<TSchema> | undefined;
482
+ };
483
+ /**
484
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
485
+ */
477
486
  declare const drizzleTransactionStorage: AsyncLocalStorage<PostgresJsDatabase<Record<string, unknown>>>;
487
+ /**
488
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
489
+ */
478
490
  declare function withDrizzleTransaction<T>(db: PostgresJsDatabase<Record<string, unknown>>, fn: (tx: PostgresJsDatabase<Record<string, unknown>>) => Promise<T>): Promise<T>;
491
+ /**
492
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
493
+ */
479
494
  declare function getDrizzleTransaction(): () => PostgresJsDatabase<Record<string, unknown>> | undefined;
480
495
  //#endregion
481
- export { PostgresDrizzleOutbox, PostgresDrizzleOutboxConfig, drizzleTransactionStorage, getDrizzleTransaction, withDrizzleTransaction };
496
+ export { PostgresDrizzleOutbox, PostgresDrizzleOutboxConfig, createDrizzleTransactionStorage, drizzleTransactionStorage, getDrizzleTransaction, outboxEvents, outboxEventsArchive, outboxStatusEnum, withDrizzleTransaction };
482
497
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../core/src/errors/errors.ts","../../../core/src/types/types.ts","../../../core/src/types/interfaces.ts","../src/schema.ts","../src/postgres-drizzle-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../core/src/errors/errors.ts","../../../core/src/types/types.ts","../../../core/src/types/interfaces.ts","../src/schema.ts","../src/postgres-drizzle-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -46,6 +46,7 @@ interface OutboxConfig {
46
46
  }
47
47
  //#endregion
48
48
  //#region src/schema.d.ts
49
+ declare const outboxStatusEnum: drizzle_orm_pg_core0.PgEnum<["created", "active", "completed", "failed"]>;
49
50
  declare const outboxEvents: drizzle_orm_pg_core0.PgTableWithColumns<{
50
51
  name: "outbox_events";
51
52
  schema: undefined;
@@ -453,19 +454,19 @@ declare const outboxEventsArchive: drizzle_orm_pg_core0.PgTableWithColumns<{
453
454
  }>;
454
455
  //#endregion
455
456
  //#region src/postgres-drizzle-outbox.d.ts
456
- interface PostgresDrizzleOutboxConfig extends OutboxConfig {
457
- db: PostgresJsDatabase<Record<string, unknown>>;
458
- getTransaction?: (() => PostgresJsDatabase<Record<string, unknown>> | undefined) | undefined;
457
+ interface PostgresDrizzleOutboxConfig<TSchema extends Record<string, unknown> = Record<string, unknown>> extends OutboxConfig {
458
+ db: PostgresJsDatabase<TSchema>;
459
+ getTransaction?: (() => PostgresJsDatabase<TSchema> | undefined) | undefined;
459
460
  tables?: {
460
461
  outboxEvents: typeof outboxEvents;
461
462
  outboxEventsArchive: typeof outboxEventsArchive;
462
463
  };
463
464
  }
464
- declare class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<string, unknown>>> {
465
+ declare class PostgresDrizzleOutbox<TSchema extends Record<string, unknown> = Record<string, unknown>> implements IOutbox<PostgresJsDatabase<TSchema>> {
465
466
  private readonly config;
466
467
  private readonly poller;
467
- constructor(config: PostgresDrizzleOutboxConfig);
468
- publish(events: BusEvent[], transaction?: PostgresJsDatabase<Record<string, unknown>>): Promise<void>;
468
+ constructor(config: PostgresDrizzleOutboxConfig<TSchema>);
469
+ publish(events: BusEvent[], transaction?: PostgresJsDatabase<TSchema>): Promise<void>;
469
470
  getFailedEvents(): Promise<FailedBusEvent[]>;
470
471
  retryEvents(eventIds: string[]): Promise<void>;
471
472
  start(handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler): void;
@@ -474,9 +475,23 @@ declare class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record
474
475
  }
475
476
  //#endregion
476
477
  //#region src/transaction-storage.d.ts
478
+ declare function createDrizzleTransactionStorage<TSchema extends Record<string, unknown> = Record<string, unknown>>(): {
479
+ storage: AsyncLocalStorage<PostgresJsDatabase<TSchema>>;
480
+ withTransaction: <T>(db: PostgresJsDatabase<TSchema>, fn: (tx: PostgresJsDatabase<TSchema>) => Promise<T>) => Promise<T>;
481
+ getTransaction: () => PostgresJsDatabase<TSchema> | undefined;
482
+ };
483
+ /**
484
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
485
+ */
477
486
  declare const drizzleTransactionStorage: AsyncLocalStorage<PostgresJsDatabase<Record<string, unknown>>>;
487
+ /**
488
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
489
+ */
478
490
  declare function withDrizzleTransaction<T>(db: PostgresJsDatabase<Record<string, unknown>>, fn: (tx: PostgresJsDatabase<Record<string, unknown>>) => Promise<T>): Promise<T>;
491
+ /**
492
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
493
+ */
479
494
  declare function getDrizzleTransaction(): () => PostgresJsDatabase<Record<string, unknown>> | undefined;
480
495
  //#endregion
481
- export { PostgresDrizzleOutbox, PostgresDrizzleOutboxConfig, drizzleTransactionStorage, getDrizzleTransaction, withDrizzleTransaction };
496
+ export { PostgresDrizzleOutbox, PostgresDrizzleOutboxConfig, createDrizzleTransactionStorage, drizzleTransactionStorage, getDrizzleTransaction, outboxEvents, outboxEventsArchive, outboxStatusEnum, withDrizzleTransaction };
482
497
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../core/src/errors/errors.ts","../../../core/src/types/types.ts","../../../core/src/types/interfaces.ts","../src/schema.ts","../src/postgres-drizzle-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":[],"mappings":";;;;;UAEiB,kBAAA;UACP,WAAW;;;AADrB;AAMsB,uBAAA,WAAW,CAAA,iBACd,kBADc,GACO,kBADP,CAAA,SAEvB,KAAA,CAFuB;EACd,OAAA,CAAA,EAEA,QAFA,GAAA,SAAA;EAAqB,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAIe,QAJf;;;;KCL5B;;QAEJ;EDJS,OAAA,ECKN,CDLM;EAMK,UAAA,ECAR,IDAmB;EACd,QAAA,CAAA,ECAN,MDAM,CAAA,MAAA,EAAA,OAAA,CAAA;CAAqB;AAErB,KCCP,cDDO,CAAA,UAAA,MAAA,GAAA,MAAA,EAAA,IAAA,OAAA,CAAA,GCCkD,QDDlD,CCC2D,CDD3D,ECC8D,CDD9D,CAAA,GAAA;EAEoC,KAAA,CAAA,EAAA,MAAA;EAH7C,UAAA,EAAA,MAAA;EAAK,aAAA,CAAA,ECKG,IDLH;;ACJP,KAuBI,YAAA,GAvBJ,CAAA,KAAA,EAuB2B,WAvB3B,EAAA,GAAA,IAAA;;;UCHS;oBACG,0BAA0B,iBAAiB;2BACpC,aAAa,wBAAwB;EFH/C,IAAA,EAAA,GAAA,GEIH,OFJG,CAAA,IAAkB,CAAA;EAMb,eAAW,EAAA,GAAA,GEDR,OFCQ,CEDA,cFCA,EAAA,CAAA;EACd,WAAA,EAAA,CAAA,QAAA,EAAA,MAAA,EAAA,EAAA,GEDoB,OFCpB,CAAA,IAAA,CAAA;;AEL4C,UA8D9C,YAAA,CA9D8C;EACpC,UAAA,CAAA,EAAA,MAAA;EAAa,aAAA,CAAA,EAAA,MAAA;EAAwB,cAAA,CAAA,EAAA,MAAA;EAClD,SAAA,CAAA,EAAA,MAAA;EACmB,mBAAA,CAAA,EAAA,MAAA;EAAR,iBAAA,CAAA,EAAA,MAAA;;;;cCEZ,mCAAY;;;;IHPR,EAAA,EGqBf,oBAAA,CAAA,QHpBmB,CAAA;MAKC,IAAA,EAAW,IAAA;MACd,SAAA,EAAA,eAAA;MAAqB,QAAA,EAAA,QAAA;MAErB,UAAA,EAAA,QAAA;MAEoC,IAAA,EAAA,MAAA;MAH7C,WAAA,EAAA,MAAA;MAAK,OAAA,EAAA,IAAA;;;;MCNH,iBAAQ,EAAA,KAAA;MAEZ,UAAA,EAAA,SAAA;MACG,UAAA,EAAA,KAAA;MACG,QAAA,EAAA,SAAA;MACD,SAAA,EAAA,SAAA;IAAM,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;IAGP,IAAA,+BAAc,CAAA;MAAoD,IAAA,EAAA,MAAA;MAAG,SAAA,EAAA,eAAA;MAAZ,QAAA,EAAA,QAAA;MAGnD,UAAA,EAAA,QAAA;MAAI,IAAA,EAAA,MAAA;MAcV,WAAY,EAAA,MAAW;;;;MC1BlB,eAAO,EAAA,KAAA;MACJ,iBAAA,EAAA,KAAA;MAA0B,UAAA,EAAA,CAAA,MAAA,EAAA,GAAA,MAAA,EAAA,CAAA;MAAiB,UAAA,EAAA,KAAA;MACpC,QAAA,EAAA,SAAA;MAAa,SAAA,EAAA,SAAA;IAAwB,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;IAClD,OAAA,+BAAA,CAAA;MACmB,IAAA,EAAA,SAAA;MAAR,SAAA,EAAA,eAAA;MACc,QAAA,EAAA,MAAA;MAAO,UAAA,EAAA,SAAA;MA0D7B,IAAA,EAAA,OAAY;;;;MCzDhB,YAcX,EAAA,KAAA;MAAA,eAAA,EAAA,KAAA;;;;;;;;;;;;;;;;;;;gBAduB,EAAA,SAAA;MAAA,UAAA,EAAA,KAAA;MAgBZ,QAAA,EAAA,SAWX;MAAA,SAAA,EAAA,SAAA;;;;;;;;;;;;;;gBAX8B,EAAA,CAAA,SAAA,EAAA,QAAA,EAAA,WAAA,EAAA,QAAA,CAAA;MAAA,UAAA,EAAA,KAAA;;;;ICVf,UAAA,+BAA4B,CAAA;MACpB,IAAA,EAAA,aAAA;MAAnB,SAAA,EAAA,eAAA;MACuC,QAAA,EAAA,QAAA;MAAnB,UAAA,EAAA,WAAA;MAED,IAAA,EAAA,MAAA;MACO,WAAA,EAAA,MAAA,GAAA,MAAA;MALqB,OAAA,EAAA,IAAA;MAAY,UAAA,EAAA,IAAA;MASpD,YAAA,EAAA,KAAsB;MAAsC,eAAA,EAAA,KAAA;MAAnB,iBAAA,EAAA,KAAA;MAIhC,UAAA,EAAA,SAAA;MAyBV,UAAA,EAAA,KAAA;MACyB,QAAA,EAAA,SAAA;MAAnB,SAAA,EAAA,SAAA;IACb,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;IAc8B,SAAA,+BAAA,CAAA;MAAR,IAAA,EAAA,YAAA;MAsBc,SAAA,EAAA,eAAA;MAYhB,QAAA,EAAA,QAAA;MAAa,UAAA,EAAA,QAAA;MAAwB,IAAA,EAAA,MAAA;MAI9C,WAAA,EAAA,MAAA;MAnF8B,OAAA,EAAA,KAAA;MAAO,UAAA,EAAA,KAAA;;;;MCrBxC,UAAA,EAAA,CAAA,MAAA,EAEV,GAAA,MAAA,EAAA,CAAA;MAFmC,UAAA,EAAA,KAAA;MAAA,QAAA,EAAA,SAAA;MAAA,SAAA,EAAA,SAAA;IAAA,CAAA,EAAA,CAAA,CAAA,EAAA,CAAA,CAAA,CAAA;IAIhB,WAAA,+BAAsB,CAAA;MACnB,IAAA,EAAA,eAAA;MAAnB,SAAA,EAAA,eAAA;MACwB,QAAA,EAAA,MAAA;MAAnB,UAAA,EAAA,aAAA;MAAwD,IAAA,MAAA;MAAR,WAAA,EAAA,MAAA;MAChD,OAAA,EAAA,KAAA;MAAR,UAAA,EAAA,KAAA;MAAO,YAAA,EAAA,KAAA;MAMM,eAAA,EAAqB,KAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cFSxB,0CAAmB;;;;QAW9B,oBAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UCrBe,2BAAA,SAAoC;MAC/C,mBAAmB;EJdR,cAAA,CAAA,EAAA,CAAA,GAAkB,GIeT,kBJdhB,CIcmC,MJdxB,CAAA,MAAA,EAAc,OAAA,CAAA,CAAA,GAAA,SAAA,CAAA,GAAA,SAAA;EAKb,MAAA,CAAA,EAAA;IACH,YAAA,EAAA,OIUM,YJVN;IAAqB,mBAAA,EAAA,OIWR,mBJXQ;EAErB,CAAA;;AADT,cIcG,qBAAA,YAAiC,OJdpC,CIc4C,kBJd5C,CIc+D,MJd/D,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,CAAA;EAAK,iBAAA,MAAA;;sBIkBO;kBAyBV,0BACM,mBAAmB,2BAChC;EHnDO,eAAQ,CAAA,CAAA,EGiEO,OHjEP,CGiEe,cHjEf,EAAA,CAAA;EAEZ,WAAA,CAAA,QAAA,EAAA,MAAA,EAAA,CAAA,EGqFiC,OHrFjC,CAAA,IAAA,CAAA;EACG,KAAA,CAAA,OAAA,EAAA,CAAA,KAAA,EGgGc,QHhGd,EAAA,GGgG2B,OHhG3B,CAAA,IAAA,CAAA,EAAA,OAAA,EGgGmD,YHhGnD,CAAA,EAAA,IAAA;EACG,IAAA,CAAA,CAAA,EGmGE,OHnGF,CAAA,IAAA,CAAA;EACD,QAAA,YAAA;;;;cINA,2BAAyB,kBAAA,mBAAA;iBAIhB,8BAChB,mBAAmB,mCACd,mBAAmB,6BAA6B,QAAQ,KAChE,QAAQ;iBAMK,qBAAA,CAAA,SACZ,mBAAmB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../core/src/errors/errors.ts","../../../core/src/types/types.ts","../../../core/src/types/interfaces.ts","../src/schema.ts","../src/postgres-drizzle-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -103,18 +103,22 @@ var PostgresDrizzleOutbox = class {
103
103
  await this.poller.stop();
104
104
  }
105
105
  async processBatch(handler) {
106
- await this.config.db.transaction(async (transaction) => {
107
- const now = /* @__PURE__ */ new Date();
106
+ const now = /* @__PURE__ */ new Date();
107
+ const lockedEvents = await this.config.db.transaction(async (transaction) => {
108
108
  const events = await transaction.select().from(this.config.tables.outboxEvents).where(or(eq(this.config.tables.outboxEvents.status, EventStatus.CREATED), and(eq(this.config.tables.outboxEvents.status, EventStatus.FAILED), lt(this.config.tables.outboxEvents.retryCount, this.config.maxRetries), lt(this.config.tables.outboxEvents.nextRetryAt, now)), and(eq(this.config.tables.outboxEvents.status, EventStatus.ACTIVE), lt(this.config.tables.outboxEvents.keepAlive, sql`${now.toISOString()}::timestamp - make_interval(secs => ${this.config.tables.outboxEvents.expireInSeconds})`)))).limit(this.config.batchSize).for("update", { skipLocked: true });
109
- if (events.length === 0) return;
109
+ if (events.length === 0) return [];
110
110
  const eventIds = events.map((event) => event.id);
111
111
  await transaction.update(this.config.tables.outboxEvents).set({
112
112
  status: EventStatus.ACTIVE,
113
113
  startedOn: now,
114
114
  keepAlive: now
115
115
  }).where(inArray(this.config.tables.outboxEvents.id, eventIds));
116
- for (const event of events) try {
117
- await handler(event);
116
+ return events;
117
+ });
118
+ if (lockedEvents.length === 0) return;
119
+ for (const event of lockedEvents) try {
120
+ await handler(event);
121
+ await this.config.db.transaction(async (transaction) => {
118
122
  await transaction.insert(this.config.tables.outboxEventsArchive).values({
119
123
  id: event.id,
120
124
  type: event.type,
@@ -127,33 +131,58 @@ var PostgresDrizzleOutbox = class {
127
131
  completedOn: /* @__PURE__ */ new Date()
128
132
  });
129
133
  await transaction.delete(this.config.tables.outboxEvents).where(eq(this.config.tables.outboxEvents.id, event.id));
130
- } catch (error) {
131
- const retryCount = event.retryCount + 1;
132
- reportEventError(this.poller.onError, error, event, retryCount, this.config.maxRetries);
133
- const delay = this.poller.calculateBackoff(retryCount);
134
- await transaction.update(this.config.tables.outboxEvents).set({
135
- status: EventStatus.FAILED,
136
- retryCount,
137
- lastError: formatErrorMessage(error),
138
- nextRetryAt: new Date(Date.now() + delay)
139
- }).where(eq(this.config.tables.outboxEvents.id, event.id));
140
- }
141
- });
134
+ });
135
+ } catch (error) {
136
+ const retryCount = event.retryCount + 1;
137
+ reportEventError(this.poller.onError, error, event, retryCount, this.config.maxRetries);
138
+ const delay = this.poller.calculateBackoff(retryCount);
139
+ await this.config.db.update(this.config.tables.outboxEvents).set({
140
+ status: EventStatus.FAILED,
141
+ retryCount,
142
+ lastError: formatErrorMessage(error),
143
+ nextRetryAt: new Date(Date.now() + delay)
144
+ }).where(eq(this.config.tables.outboxEvents.id, event.id));
145
+ }
142
146
  }
143
147
  };
144
148
 
145
149
  //#endregion
146
150
  //#region src/transaction-storage.ts
151
+ function createDrizzleTransactionStorage() {
152
+ const storage = new AsyncLocalStorage();
153
+ async function withTransaction(db, fn) {
154
+ return db.transaction(async (tx) => {
155
+ return storage.run(tx, () => fn(tx));
156
+ });
157
+ }
158
+ function getTransaction() {
159
+ return storage.getStore();
160
+ }
161
+ return {
162
+ storage,
163
+ withTransaction,
164
+ getTransaction
165
+ };
166
+ }
167
+ /**
168
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
169
+ */
147
170
  const drizzleTransactionStorage = new AsyncLocalStorage();
171
+ /**
172
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
173
+ */
148
174
  async function withDrizzleTransaction(db, fn) {
149
175
  return db.transaction(async (tx) => {
150
176
  return drizzleTransactionStorage.run(tx, () => fn(tx));
151
177
  });
152
178
  }
179
+ /**
180
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
181
+ */
153
182
  function getDrizzleTransaction() {
154
183
  return () => drizzleTransactionStorage.getStore();
155
184
  }
156
185
 
157
186
  //#endregion
158
- export { PostgresDrizzleOutbox, drizzleTransactionStorage, getDrizzleTransaction, withDrizzleTransaction };
187
+ export { PostgresDrizzleOutbox, createDrizzleTransactionStorage, drizzleTransactionStorage, getDrizzleTransaction, outboxEvents, outboxEventsArchive, outboxStatusEnum, withDrizzleTransaction };
159
188
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["failedEvent: FailedBusEvent","error: unknown"],"sources":["../src/schema.ts","../src/postgres-drizzle-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":["import { integer, jsonb, pgEnum, pgTable, text, timestamp, uuid } from \"drizzle-orm/pg-core\"\n\nexport const outboxStatusEnum = pgEnum(\"outbox_status\", [\n \"created\",\n \"active\",\n \"completed\",\n \"failed\",\n])\n\nexport const outboxEvents = pgTable(\"outbox_events\", {\n id: uuid(\"id\").primaryKey(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n occurredAt: timestamp(\"occurred_at\").notNull(),\n status: outboxStatusEnum(\"status\").notNull().default(\"created\"),\n retryCount: integer(\"retry_count\").notNull().default(0),\n lastError: text(\"last_error\"),\n nextRetryAt: timestamp(\"next_retry_at\"),\n createdOn: timestamp(\"created_on\").notNull().defaultNow(),\n startedOn: timestamp(\"started_on\"),\n completedOn: timestamp(\"completed_on\"),\n keepAlive: timestamp(\"keep_alive\"),\n expireInSeconds: integer(\"expire_in_seconds\").notNull().default(300),\n})\n\nexport const outboxEventsArchive = pgTable(\"outbox_events_archive\", {\n id: uuid(\"id\").primaryKey(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n occurredAt: timestamp(\"occurred_at\").notNull(),\n status: outboxStatusEnum(\"status\").notNull(),\n retryCount: integer(\"retry_count\").notNull(),\n lastError: text(\"last_error\"),\n createdOn: timestamp(\"created_on\").notNull(),\n startedOn: timestamp(\"started_on\"),\n completedOn: timestamp(\"completed_on\").notNull(),\n})\n","import { and, eq, inArray, lt, or, sql } from \"drizzle-orm\"\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\"\nimport {\n type BusEvent,\n type ErrorHandler,\n EventStatus,\n type FailedBusEvent,\n formatErrorMessage,\n type IOutbox,\n type OutboxConfig,\n PollingService,\n reportEventError,\n} from \"outbox-event-bus\"\nimport { outboxEvents, outboxEventsArchive } from \"./schema\"\n\nexport interface PostgresDrizzleOutboxConfig extends OutboxConfig {\n db: PostgresJsDatabase<Record<string, unknown>>\n getTransaction?: (() => PostgresJsDatabase<Record<string, unknown>> | undefined) | undefined\n tables?: {\n outboxEvents: typeof outboxEvents\n outboxEventsArchive: typeof outboxEventsArchive\n }\n}\n\nexport class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<string, unknown>>> {\n private readonly config: Required<PostgresDrizzleOutboxConfig>\n private readonly poller: PollingService\n\n constructor(config: PostgresDrizzleOutboxConfig) {\n this.config = {\n batchSize: config.batchSize ?? 50,\n pollIntervalMs: config.pollIntervalMs ?? 1000,\n maxRetries: config.maxRetries ?? 5,\n baseBackoffMs: config.baseBackoffMs ?? 1000,\n processingTimeoutMs: config.processingTimeoutMs ?? 30000,\n maxErrorBackoffMs: config.maxErrorBackoffMs ?? 30000,\n db: config.db,\n getTransaction: config.getTransaction,\n tables: config.tables ?? {\n outboxEvents,\n outboxEventsArchive,\n },\n }\n\n this.poller = new PollingService({\n pollIntervalMs: this.config.pollIntervalMs,\n baseBackoffMs: this.config.baseBackoffMs,\n maxErrorBackoffMs: this.config.maxErrorBackoffMs,\n processBatch: (handler) => this.processBatch(handler),\n })\n }\n\n async publish(\n events: BusEvent[],\n transaction?: PostgresJsDatabase<Record<string, unknown>>\n ): Promise<void> {\n const executor = transaction ?? this.config.getTransaction?.() ?? this.config.db\n\n await executor.insert(this.config.tables.outboxEvents).values(\n events.map((event) => ({\n id: event.id,\n type: event.type,\n payload: event.payload,\n occurredAt: event.occurredAt,\n status: EventStatus.CREATED,\n }))\n )\n }\n\n async getFailedEvents(): Promise<FailedBusEvent[]> {\n const events = await this.config.db\n .select()\n .from(this.config.tables.outboxEvents)\n .where(eq(this.config.tables.outboxEvents.status, EventStatus.FAILED))\n .orderBy(sql`${this.config.tables.outboxEvents.occurredAt} DESC`)\n .limit(100)\n\n return events.map((event) => {\n const failedEvent: FailedBusEvent = {\n id: event.id,\n type: event.type,\n payload: event.payload as any,\n occurredAt: event.occurredAt,\n retryCount: event.retryCount,\n }\n if (event.lastError) failedEvent.error = event.lastError\n if (event.startedOn) failedEvent.lastAttemptAt = event.startedOn\n return failedEvent\n })\n }\n\n async retryEvents(eventIds: string[]): Promise<void> {\n await this.config.db\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.CREATED,\n retryCount: 0,\n nextRetryAt: null,\n lastError: null,\n })\n .where(inArray(this.config.tables.outboxEvents.id, eventIds))\n }\n\n start(handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler): void {\n this.poller.start(handler, onError)\n }\n\n async stop(): Promise<void> {\n await this.poller.stop()\n }\n\n private async processBatch(handler: (event: BusEvent) => Promise<void>) {\n await this.config.db.transaction(async (transaction) => {\n const now = new Date()\n\n // Select events that are:\n // 1. New (status = created)\n // 2. Failed but can be retried (retry count < max AND retry time has passed)\n // 3. Active but stuck/timed out (keepAlive is older than now - expireInSeconds)\n const events = await transaction\n .select()\n .from(this.config.tables.outboxEvents)\n .where(\n or(\n eq(this.config.tables.outboxEvents.status, EventStatus.CREATED),\n and(\n eq(this.config.tables.outboxEvents.status, EventStatus.FAILED),\n lt(this.config.tables.outboxEvents.retryCount, this.config.maxRetries),\n lt(this.config.tables.outboxEvents.nextRetryAt, now)\n ),\n and(\n eq(this.config.tables.outboxEvents.status, EventStatus.ACTIVE),\n // Check if event is stuck: keepAlive is older than (now - expireInSeconds)\n // This uses PostgreSQL's make_interval to subtract expireInSeconds from current timestamp\n lt(\n this.config.tables.outboxEvents.keepAlive,\n sql`${now.toISOString()}::timestamp - make_interval(secs => ${this.config.tables.outboxEvents.expireInSeconds})`\n )\n )\n )\n )\n .limit(this.config.batchSize)\n .for(\"update\", { skipLocked: true })\n\n if (events.length === 0) return\n\n const eventIds = events.map((event) => event.id)\n\n await transaction\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.ACTIVE,\n startedOn: now,\n keepAlive: now,\n })\n .where(inArray(this.config.tables.outboxEvents.id, eventIds))\n\n for (const event of events) {\n try {\n await handler(event)\n // Archive successful event immediately\n await transaction.insert(this.config.tables.outboxEventsArchive).values({\n id: event.id,\n type: event.type,\n payload: event.payload,\n occurredAt: event.occurredAt,\n status: EventStatus.COMPLETED,\n retryCount: event.retryCount,\n createdOn: event.createdOn,\n startedOn: now,\n completedOn: new Date(),\n })\n await transaction\n .delete(this.config.tables.outboxEvents)\n .where(eq(this.config.tables.outboxEvents.id, event.id))\n } catch (error: unknown) {\n const retryCount = event.retryCount + 1\n reportEventError(this.poller.onError, error, event, retryCount, this.config.maxRetries)\n\n // Mark this specific event as failed\n const delay = this.poller.calculateBackoff(retryCount)\n await transaction\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.FAILED,\n retryCount,\n lastError: formatErrorMessage(error),\n nextRetryAt: new Date(Date.now() + delay),\n })\n .where(eq(this.config.tables.outboxEvents.id, event.id))\n }\n }\n })\n }\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\"\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\"\n\nexport const drizzleTransactionStorage = new AsyncLocalStorage<\n PostgresJsDatabase<Record<string, unknown>>\n>()\n\nexport async function withDrizzleTransaction<T>(\n db: PostgresJsDatabase<Record<string, unknown>>,\n fn: (tx: PostgresJsDatabase<Record<string, unknown>>) => Promise<T>\n): Promise<T> {\n return db.transaction(async (tx) => {\n return drizzleTransactionStorage.run(tx, () => fn(tx))\n })\n}\n\nexport function getDrizzleTransaction(): () =>\n | PostgresJsDatabase<Record<string, unknown>>\n | undefined {\n return () => drizzleTransactionStorage.getStore()\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["failedEvent: FailedBusEvent","error: unknown"],"sources":["../src/schema.ts","../src/postgres-drizzle-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":["import {\n integer,\n jsonb,\n pgEnum,\n pgTable,\n text,\n timestamp,\n uuid,\n} from \"drizzle-orm/pg-core\"\n\n\nexport const outboxStatusEnum = pgEnum(\"outbox_status\", [\n \"created\",\n \"active\",\n \"completed\",\n \"failed\",\n])\n\nexport const outboxEvents = pgTable(\"outbox_events\", {\n id: uuid(\"id\").primaryKey(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n occurredAt: timestamp(\"occurred_at\").notNull(),\n status: outboxStatusEnum(\"status\").notNull().default(\"created\"),\n retryCount: integer(\"retry_count\").notNull().default(0),\n lastError: text(\"last_error\"),\n nextRetryAt: timestamp(\"next_retry_at\"),\n createdOn: timestamp(\"created_on\").notNull().defaultNow(),\n startedOn: timestamp(\"started_on\"),\n completedOn: timestamp(\"completed_on\"),\n keepAlive: timestamp(\"keep_alive\"),\n expireInSeconds: integer(\"expire_in_seconds\").notNull().default(300),\n})\n\nexport const outboxEventsArchive = pgTable(\"outbox_events_archive\", {\n id: uuid(\"id\").primaryKey(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n occurredAt: timestamp(\"occurred_at\").notNull(),\n status: outboxStatusEnum(\"status\").notNull(),\n retryCount: integer(\"retry_count\").notNull(),\n lastError: text(\"last_error\"),\n createdOn: timestamp(\"created_on\").notNull(),\n startedOn: timestamp(\"started_on\"),\n completedOn: timestamp(\"completed_on\").notNull(),\n})\n","import { and, eq, inArray, lt, or, sql } from \"drizzle-orm\"\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\"\nimport {\n type BusEvent,\n type ErrorHandler,\n EventStatus,\n type FailedBusEvent,\n formatErrorMessage,\n type IOutbox,\n type OutboxConfig,\n PollingService,\n reportEventError,\n} from \"outbox-event-bus\"\nimport { outboxEvents, outboxEventsArchive } from \"./schema\"\n\nexport interface PostgresDrizzleOutboxConfig<TSchema extends Record<string, unknown> = Record<string, unknown>> extends OutboxConfig {\n db: PostgresJsDatabase<TSchema>\n getTransaction?: (() => PostgresJsDatabase<TSchema> | undefined) | undefined\n tables?: {\n outboxEvents: typeof outboxEvents\n outboxEventsArchive: typeof outboxEventsArchive\n }\n}\n\nexport class PostgresDrizzleOutbox<TSchema extends Record<string, unknown> = Record<string, unknown>> implements IOutbox<PostgresJsDatabase<TSchema>> {\n private readonly config: Required<PostgresDrizzleOutboxConfig<TSchema>>\n private readonly poller: PollingService\n\n constructor(config: PostgresDrizzleOutboxConfig<TSchema>) {\n this.config = {\n batchSize: config.batchSize ?? 50,\n pollIntervalMs: config.pollIntervalMs ?? 1000,\n maxRetries: config.maxRetries ?? 5,\n baseBackoffMs: config.baseBackoffMs ?? 1000,\n processingTimeoutMs: config.processingTimeoutMs ?? 30000,\n maxErrorBackoffMs: config.maxErrorBackoffMs ?? 30000,\n db: config.db,\n getTransaction: config.getTransaction,\n tables: config.tables ?? {\n outboxEvents,\n outboxEventsArchive,\n },\n }\n\n this.poller = new PollingService({\n pollIntervalMs: this.config.pollIntervalMs,\n baseBackoffMs: this.config.baseBackoffMs,\n maxErrorBackoffMs: this.config.maxErrorBackoffMs,\n processBatch: (handler) => this.processBatch(handler),\n })\n }\n\n async publish(\n events: BusEvent[],\n transaction?: PostgresJsDatabase<TSchema>\n ): Promise<void> {\n const executor = transaction ?? this.config.getTransaction?.() ?? this.config.db\n\n await executor.insert(this.config.tables.outboxEvents).values(\n events.map((event) => ({\n id: event.id,\n type: event.type,\n payload: event.payload,\n occurredAt: event.occurredAt,\n status: EventStatus.CREATED,\n }))\n )\n }\n\n async getFailedEvents(): Promise<FailedBusEvent[]> {\n const events = await this.config.db\n .select()\n .from(this.config.tables.outboxEvents)\n .where(eq(this.config.tables.outboxEvents.status, EventStatus.FAILED))\n .orderBy(sql`${this.config.tables.outboxEvents.occurredAt} DESC`)\n .limit(100)\n\n return events.map((event) => {\n const failedEvent: FailedBusEvent = {\n id: event.id,\n type: event.type,\n payload: event.payload as any,\n occurredAt: event.occurredAt,\n retryCount: event.retryCount,\n }\n if (event.lastError) failedEvent.error = event.lastError\n if (event.startedOn) failedEvent.lastAttemptAt = event.startedOn\n return failedEvent\n })\n }\n\n async retryEvents(eventIds: string[]): Promise<void> {\n await this.config.db\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.CREATED,\n retryCount: 0,\n nextRetryAt: null,\n lastError: null,\n })\n .where(inArray(this.config.tables.outboxEvents.id, eventIds))\n }\n\n start(handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler): void {\n this.poller.start(handler, onError)\n }\n\n async stop(): Promise<void> {\n await this.poller.stop()\n }\n\n private async processBatch(handler: (event: BusEvent) => Promise<void>) {\n const now = new Date()\n\n const lockedEvents = await this.config.db.transaction(async (transaction) => {\n const events = await transaction\n .select()\n .from(this.config.tables.outboxEvents)\n .where(\n or(\n eq(this.config.tables.outboxEvents.status, EventStatus.CREATED),\n and(\n eq(this.config.tables.outboxEvents.status, EventStatus.FAILED),\n lt(this.config.tables.outboxEvents.retryCount, this.config.maxRetries),\n lt(this.config.tables.outboxEvents.nextRetryAt, now)\n ),\n and(\n eq(this.config.tables.outboxEvents.status, EventStatus.ACTIVE),\n lt(\n this.config.tables.outboxEvents.keepAlive,\n sql`${now.toISOString()}::timestamp - make_interval(secs => ${this.config.tables.outboxEvents.expireInSeconds})`\n )\n )\n )\n )\n .limit(this.config.batchSize)\n .for(\"update\", { skipLocked: true })\n\n if (events.length === 0) return []\n\n const eventIds = events.map((event) => event.id)\n\n await transaction\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.ACTIVE,\n startedOn: now,\n keepAlive: now,\n })\n .where(inArray(this.config.tables.outboxEvents.id, eventIds))\n\n return events\n })\n\n if (lockedEvents.length === 0) return\n\n // 2. Process events outside of the transaction\n for (const event of lockedEvents) {\n try {\n await handler(event)\n\n // use the main db connection for individual updates to avoid long transactions\n await this.config.db.transaction(async (transaction) => {\n await transaction.insert(this.config.tables.outboxEventsArchive).values({\n id: event.id,\n type: event.type,\n payload: event.payload as any,\n occurredAt: event.occurredAt,\n status: EventStatus.COMPLETED,\n retryCount: event.retryCount,\n createdOn: event.createdOn,\n startedOn: now,\n completedOn: new Date(),\n })\n await transaction\n .delete(this.config.tables.outboxEvents)\n .where(eq(this.config.tables.outboxEvents.id, event.id))\n })\n } catch (error: unknown) {\n const retryCount = event.retryCount + 1\n reportEventError(this.poller.onError, error, event, retryCount, this.config.maxRetries)\n\n const delay = this.poller.calculateBackoff(retryCount)\n await this.config.db\n .update(this.config.tables.outboxEvents)\n .set({\n status: EventStatus.FAILED,\n retryCount,\n lastError: formatErrorMessage(error),\n nextRetryAt: new Date(Date.now() + delay),\n })\n .where(eq(this.config.tables.outboxEvents.id, event.id))\n }\n }\n }\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\"\nimport type { PostgresJsDatabase } from \"drizzle-orm/postgres-js\"\n\nexport function createDrizzleTransactionStorage<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n>() {\n const storage = new AsyncLocalStorage<PostgresJsDatabase<TSchema>>()\n\n async function withTransaction<T>(\n db: PostgresJsDatabase<TSchema>,\n fn: (tx: PostgresJsDatabase<TSchema>) => Promise<T>\n ): Promise<T> {\n return db.transaction(async (tx) => {\n return storage.run(tx, () => fn(tx))\n })\n }\n\n function getTransaction(): PostgresJsDatabase<TSchema> | undefined {\n return storage.getStore()\n }\n\n return { storage, withTransaction, getTransaction }\n}\n\n/**\n * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.\n */\nexport const drizzleTransactionStorage = new AsyncLocalStorage<\n PostgresJsDatabase<Record<string, unknown>>\n>()\n\n/**\n * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.\n */\nexport async function withDrizzleTransaction<T>(\n db: PostgresJsDatabase<Record<string, unknown>>,\n fn: (tx: PostgresJsDatabase<Record<string, unknown>>) => Promise<T>\n): Promise<T> {\n return db.transaction(async (tx) => {\n return drizzleTransactionStorage.run(tx, () => fn(tx))\n })\n}\n\n/**\n * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.\n */\nexport function getDrizzleTransaction(): () =>\n | PostgresJsDatabase<Record<string, unknown>>\n | undefined {\n return () => drizzleTransactionStorage.getStore()\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outbox-event-bus/postgres-drizzle-outbox",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./postgres-drizzle-outbox"
2
+ export * from "./schema"
2
3
  export * from "./transaction-storage"
@@ -299,7 +299,7 @@ describe("PostgresDrizzleOutbox E2E", () => {
299
299
  worker.start(handler, (error) => console.error(`Worker ${i} Error:`, error))
300
300
  }
301
301
 
302
- const maxWaitTime = 10000
302
+ const maxWaitTime = 20000
303
303
  const startTime = Date.now()
304
304
 
305
305
  while (processedEvents.length < eventCount && Date.now() - startTime < maxWaitTime) {
@@ -13,20 +13,20 @@ import {
13
13
  } from "outbox-event-bus"
14
14
  import { outboxEvents, outboxEventsArchive } from "./schema"
15
15
 
16
- export interface PostgresDrizzleOutboxConfig extends OutboxConfig {
17
- db: PostgresJsDatabase<Record<string, unknown>>
18
- getTransaction?: (() => PostgresJsDatabase<Record<string, unknown>> | undefined) | undefined
16
+ export interface PostgresDrizzleOutboxConfig<TSchema extends Record<string, unknown> = Record<string, unknown>> extends OutboxConfig {
17
+ db: PostgresJsDatabase<TSchema>
18
+ getTransaction?: (() => PostgresJsDatabase<TSchema> | undefined) | undefined
19
19
  tables?: {
20
20
  outboxEvents: typeof outboxEvents
21
21
  outboxEventsArchive: typeof outboxEventsArchive
22
22
  }
23
23
  }
24
24
 
25
- export class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<string, unknown>>> {
26
- private readonly config: Required<PostgresDrizzleOutboxConfig>
25
+ export class PostgresDrizzleOutbox<TSchema extends Record<string, unknown> = Record<string, unknown>> implements IOutbox<PostgresJsDatabase<TSchema>> {
26
+ private readonly config: Required<PostgresDrizzleOutboxConfig<TSchema>>
27
27
  private readonly poller: PollingService
28
28
 
29
- constructor(config: PostgresDrizzleOutboxConfig) {
29
+ constructor(config: PostgresDrizzleOutboxConfig<TSchema>) {
30
30
  this.config = {
31
31
  batchSize: config.batchSize ?? 50,
32
32
  pollIntervalMs: config.pollIntervalMs ?? 1000,
@@ -52,7 +52,7 @@ export class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<
52
52
 
53
53
  async publish(
54
54
  events: BusEvent[],
55
- transaction?: PostgresJsDatabase<Record<string, unknown>>
55
+ transaction?: PostgresJsDatabase<TSchema>
56
56
  ): Promise<void> {
57
57
  const executor = transaction ?? this.config.getTransaction?.() ?? this.config.db
58
58
 
@@ -110,13 +110,9 @@ export class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<
110
110
  }
111
111
 
112
112
  private async processBatch(handler: (event: BusEvent) => Promise<void>) {
113
- await this.config.db.transaction(async (transaction) => {
114
- const now = new Date()
113
+ const now = new Date()
115
114
 
116
- // Select events that are:
117
- // 1. New (status = created)
118
- // 2. Failed but can be retried (retry count < max AND retry time has passed)
119
- // 3. Active but stuck/timed out (keepAlive is older than now - expireInSeconds)
115
+ const lockedEvents = await this.config.db.transaction(async (transaction) => {
120
116
  const events = await transaction
121
117
  .select()
122
118
  .from(this.config.tables.outboxEvents)
@@ -130,8 +126,6 @@ export class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<
130
126
  ),
131
127
  and(
132
128
  eq(this.config.tables.outboxEvents.status, EventStatus.ACTIVE),
133
- // Check if event is stuck: keepAlive is older than (now - expireInSeconds)
134
- // This uses PostgreSQL's make_interval to subtract expireInSeconds from current timestamp
135
129
  lt(
136
130
  this.config.tables.outboxEvents.keepAlive,
137
131
  sql`${now.toISOString()}::timestamp - make_interval(secs => ${this.config.tables.outboxEvents.expireInSeconds})`
@@ -142,7 +136,7 @@ export class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<
142
136
  .limit(this.config.batchSize)
143
137
  .for("update", { skipLocked: true })
144
138
 
145
- if (events.length === 0) return
139
+ if (events.length === 0) return []
146
140
 
147
141
  const eventIds = events.map((event) => event.id)
148
142
 
@@ -155,14 +149,22 @@ export class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<
155
149
  })
156
150
  .where(inArray(this.config.tables.outboxEvents.id, eventIds))
157
151
 
158
- for (const event of events) {
159
- try {
160
- await handler(event)
161
- // Archive successful event immediately
152
+ return events
153
+ })
154
+
155
+ if (lockedEvents.length === 0) return
156
+
157
+ // 2. Process events outside of the transaction
158
+ for (const event of lockedEvents) {
159
+ try {
160
+ await handler(event)
161
+
162
+ // use the main db connection for individual updates to avoid long transactions
163
+ await this.config.db.transaction(async (transaction) => {
162
164
  await transaction.insert(this.config.tables.outboxEventsArchive).values({
163
165
  id: event.id,
164
166
  type: event.type,
165
- payload: event.payload,
167
+ payload: event.payload as any,
166
168
  occurredAt: event.occurredAt,
167
169
  status: EventStatus.COMPLETED,
168
170
  retryCount: event.retryCount,
@@ -173,23 +175,22 @@ export class PostgresDrizzleOutbox implements IOutbox<PostgresJsDatabase<Record<
173
175
  await transaction
174
176
  .delete(this.config.tables.outboxEvents)
175
177
  .where(eq(this.config.tables.outboxEvents.id, event.id))
176
- } catch (error: unknown) {
177
- const retryCount = event.retryCount + 1
178
- reportEventError(this.poller.onError, error, event, retryCount, this.config.maxRetries)
179
-
180
- // Mark this specific event as failed
181
- const delay = this.poller.calculateBackoff(retryCount)
182
- await transaction
183
- .update(this.config.tables.outboxEvents)
184
- .set({
185
- status: EventStatus.FAILED,
186
- retryCount,
187
- lastError: formatErrorMessage(error),
188
- nextRetryAt: new Date(Date.now() + delay),
189
- })
190
- .where(eq(this.config.tables.outboxEvents.id, event.id))
191
- }
178
+ })
179
+ } catch (error: unknown) {
180
+ const retryCount = event.retryCount + 1
181
+ reportEventError(this.poller.onError, error, event, retryCount, this.config.maxRetries)
182
+
183
+ const delay = this.poller.calculateBackoff(retryCount)
184
+ await this.config.db
185
+ .update(this.config.tables.outboxEvents)
186
+ .set({
187
+ status: EventStatus.FAILED,
188
+ retryCount,
189
+ lastError: formatErrorMessage(error),
190
+ nextRetryAt: new Date(Date.now() + delay),
191
+ })
192
+ .where(eq(this.config.tables.outboxEvents.id, event.id))
192
193
  }
193
- })
194
+ }
194
195
  }
195
196
  }
package/src/schema.ts CHANGED
@@ -1,4 +1,13 @@
1
- import { integer, jsonb, pgEnum, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
1
+ import {
2
+ integer,
3
+ jsonb,
4
+ pgEnum,
5
+ pgTable,
6
+ text,
7
+ timestamp,
8
+ uuid,
9
+ } from "drizzle-orm/pg-core"
10
+
2
11
 
3
12
  export const outboxStatusEnum = pgEnum("outbox_status", [
4
13
  "created",
@@ -1,10 +1,37 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks"
2
2
  import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"
3
3
 
4
+ export function createDrizzleTransactionStorage<
5
+ TSchema extends Record<string, unknown> = Record<string, unknown>,
6
+ >() {
7
+ const storage = new AsyncLocalStorage<PostgresJsDatabase<TSchema>>()
8
+
9
+ async function withTransaction<T>(
10
+ db: PostgresJsDatabase<TSchema>,
11
+ fn: (tx: PostgresJsDatabase<TSchema>) => Promise<T>
12
+ ): Promise<T> {
13
+ return db.transaction(async (tx) => {
14
+ return storage.run(tx, () => fn(tx))
15
+ })
16
+ }
17
+
18
+ function getTransaction(): PostgresJsDatabase<TSchema> | undefined {
19
+ return storage.getStore()
20
+ }
21
+
22
+ return { storage, withTransaction, getTransaction }
23
+ }
24
+
25
+ /**
26
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
27
+ */
4
28
  export const drizzleTransactionStorage = new AsyncLocalStorage<
5
29
  PostgresJsDatabase<Record<string, unknown>>
6
30
  >()
7
31
 
32
+ /**
33
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
34
+ */
8
35
  export async function withDrizzleTransaction<T>(
9
36
  db: PostgresJsDatabase<Record<string, unknown>>,
10
37
  fn: (tx: PostgresJsDatabase<Record<string, unknown>>) => Promise<T>
@@ -14,6 +41,9 @@ export async function withDrizzleTransaction<T>(
14
41
  })
15
42
  }
16
43
 
44
+ /**
45
+ * @deprecated Use `createDrizzleTransactionStorage()` for type-safe schema support.
46
+ */
17
47
  export function getDrizzleTransaction(): () =>
18
48
  | PostgresJsDatabase<Record<string, unknown>>
19
49
  | undefined {