@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 +39 -5
- package/dist/index.cjs +50 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +22 -7
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +47 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/integration.e2e.ts +1 -1
- package/src/postgres-drizzle-outbox.ts +39 -38
- package/src/schema.ts +10 -1
- package/src/transaction-storage.ts +30 -0
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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<
|
|
458
|
-
getTransaction?: (() => PostgresJsDatabase<
|
|
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
|
|
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<
|
|
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
|
package/dist/index.d.cts.map
CHANGED
|
@@ -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":";;;;;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;;;;
|
|
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<
|
|
458
|
-
getTransaction?: (() => PostgresJsDatabase<
|
|
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
|
|
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<
|
|
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
|
package/dist/index.d.mts.map
CHANGED
|
@@ -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;;;;
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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":";;;;;;AAEA,MAAa,mBAAmB,OAAO,iBAAiB;CACtD;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAa,eAAe,QAAQ,iBAAiB;CACnD,IAAI,KAAK,KAAK,CAAC,YAAY;CAC3B,MAAM,KAAK,OAAO,CAAC,SAAS;CAC5B,SAAS,MAAM,UAAU,CAAC,SAAS;CACnC,YAAY,UAAU,cAAc,CAAC,SAAS;CAC9C,QAAQ,iBAAiB,SAAS,CAAC,SAAS,CAAC,QAAQ,UAAU;CAC/D,YAAY,QAAQ,cAAc,CAAC,SAAS,CAAC,QAAQ,EAAE;CACvD,WAAW,KAAK,aAAa;CAC7B,aAAa,UAAU,gBAAgB;CACvC,WAAW,UAAU,aAAa,CAAC,SAAS,CAAC,YAAY;CACzD,WAAW,UAAU,aAAa;CAClC,aAAa,UAAU,eAAe;CACtC,WAAW,UAAU,aAAa;CAClC,iBAAiB,QAAQ,oBAAoB,CAAC,SAAS,CAAC,QAAQ,IAAI;CACrE,CAAC;AAEF,MAAa,sBAAsB,QAAQ,yBAAyB;CAClE,IAAI,KAAK,KAAK,CAAC,YAAY;CAC3B,MAAM,KAAK,OAAO,CAAC,SAAS;CAC5B,SAAS,MAAM,UAAU,CAAC,SAAS;CACnC,YAAY,UAAU,cAAc,CAAC,SAAS;CAC9C,QAAQ,iBAAiB,SAAS,CAAC,SAAS;CAC5C,YAAY,QAAQ,cAAc,CAAC,SAAS;CAC5C,WAAW,KAAK,aAAa;CAC7B,WAAW,UAAU,aAAa,CAAC,SAAS;CAC5C,WAAW,UAAU,aAAa;CAClC,aAAa,UAAU,eAAe,CAAC,SAAS;CACjD,CAAC;;;;ACZF,IAAa,wBAAb,MAAmG;CACjG,AAAiB;CACjB,AAAiB;CAEjB,YAAY,QAAqC;AAC/C,OAAK,SAAS;GACZ,WAAW,OAAO,aAAa;GAC/B,gBAAgB,OAAO,kBAAkB;GACzC,YAAY,OAAO,cAAc;GACjC,eAAe,OAAO,iBAAiB;GACvC,qBAAqB,OAAO,uBAAuB;GACnD,mBAAmB,OAAO,qBAAqB;GAC/C,IAAI,OAAO;GACX,gBAAgB,OAAO;GACvB,QAAQ,OAAO,UAAU;IACvB;IACA;IACD;GACF;AAED,OAAK,SAAS,IAAI,eAAe;GAC/B,gBAAgB,KAAK,OAAO;GAC5B,eAAe,KAAK,OAAO;GAC3B,mBAAmB,KAAK,OAAO;GAC/B,eAAe,YAAY,KAAK,aAAa,QAAQ;GACtD,CAAC;;CAGJ,MAAM,QACJ,QACA,aACe;AAGf,SAFiB,eAAe,KAAK,OAAO,kBAAkB,IAAI,KAAK,OAAO,IAE/D,OAAO,KAAK,OAAO,OAAO,aAAa,CAAC,OACrD,OAAO,KAAK,WAAW;GACrB,IAAI,MAAM;GACV,MAAM,MAAM;GACZ,SAAS,MAAM;GACf,YAAY,MAAM;GAClB,QAAQ,YAAY;GACrB,EAAE,CACJ;;CAGH,MAAM,kBAA6C;AAQjD,UAPe,MAAM,KAAK,OAAO,GAC9B,QAAQ,CACR,KAAK,KAAK,OAAO,OAAO,aAAa,CACrC,MAAM,GAAG,KAAK,OAAO,OAAO,aAAa,QAAQ,YAAY,OAAO,CAAC,CACrE,QAAQ,GAAG,GAAG,KAAK,OAAO,OAAO,aAAa,WAAW,OAAO,CAChE,MAAM,IAAI,EAEC,KAAK,UAAU;GAC3B,MAAMA,cAA8B;IAClC,IAAI,MAAM;IACV,MAAM,MAAM;IACZ,SAAS,MAAM;IACf,YAAY,MAAM;IAClB,YAAY,MAAM;IACnB;AACD,OAAI,MAAM,UAAW,aAAY,QAAQ,MAAM;AAC/C,OAAI,MAAM,UAAW,aAAY,gBAAgB,MAAM;AACvD,UAAO;IACP;;CAGJ,MAAM,YAAY,UAAmC;AACnD,QAAM,KAAK,OAAO,GACf,OAAO,KAAK,OAAO,OAAO,aAAa,CACvC,IAAI;GACH,QAAQ,YAAY;GACpB,YAAY;GACZ,aAAa;GACb,WAAW;GACZ,CAAC,CACD,MAAM,QAAQ,KAAK,OAAO,OAAO,aAAa,IAAI,SAAS,CAAC;;CAGjE,MAAM,SAA6C,SAA6B;AAC9E,OAAK,OAAO,MAAM,SAAS,QAAQ;;CAGrC,MAAM,OAAsB;AAC1B,QAAM,KAAK,OAAO,MAAM;;CAG1B,MAAc,aAAa,SAA6C;AACtE,QAAM,KAAK,OAAO,GAAG,YAAY,OAAO,gBAAgB;GACtD,MAAM,sBAAM,IAAI,MAAM;GAMtB,MAAM,SAAS,MAAM,YAClB,QAAQ,CACR,KAAK,KAAK,OAAO,OAAO,aAAa,CACrC,MACC,GACE,GAAG,KAAK,OAAO,OAAO,aAAa,QAAQ,YAAY,QAAQ,EAC/D,IACE,GAAG,KAAK,OAAO,OAAO,aAAa,QAAQ,YAAY,OAAO,EAC9D,GAAG,KAAK,OAAO,OAAO,aAAa,YAAY,KAAK,OAAO,WAAW,EACtE,GAAG,KAAK,OAAO,OAAO,aAAa,aAAa,IAAI,CACrD,EACD,IACE,GAAG,KAAK,OAAO,OAAO,aAAa,QAAQ,YAAY,OAAO,EAG9D,GACE,KAAK,OAAO,OAAO,aAAa,WAChC,GAAG,GAAG,IAAI,aAAa,CAAC,sCAAsC,KAAK,OAAO,OAAO,aAAa,gBAAgB,GAC/G,CACF,CACF,CACF,CACA,MAAM,KAAK,OAAO,UAAU,CAC5B,IAAI,UAAU,EAAE,YAAY,MAAM,CAAC;AAEtC,OAAI,OAAO,WAAW,EAAG;GAEzB,MAAM,WAAW,OAAO,KAAK,UAAU,MAAM,GAAG;AAEhD,SAAM,YACH,OAAO,KAAK,OAAO,OAAO,aAAa,CACvC,IAAI;IACH,QAAQ,YAAY;IACpB,WAAW;IACX,WAAW;IACZ,CAAC,CACD,MAAM,QAAQ,KAAK,OAAO,OAAO,aAAa,IAAI,SAAS,CAAC;AAE/D,QAAK,MAAM,SAAS,OAClB,KAAI;AACF,UAAM,QAAQ,MAAM;AAEpB,UAAM,YAAY,OAAO,KAAK,OAAO,OAAO,oBAAoB,CAAC,OAAO;KACtE,IAAI,MAAM;KACV,MAAM,MAAM;KACZ,SAAS,MAAM;KACf,YAAY,MAAM;KAClB,QAAQ,YAAY;KACpB,YAAY,MAAM;KAClB,WAAW,MAAM;KACjB,WAAW;KACX,6BAAa,IAAI,MAAM;KACxB,CAAC;AACF,UAAM,YACH,OAAO,KAAK,OAAO,OAAO,aAAa,CACvC,MAAM,GAAG,KAAK,OAAO,OAAO,aAAa,IAAI,MAAM,GAAG,CAAC;YACnDC,OAAgB;IACvB,MAAM,aAAa,MAAM,aAAa;AACtC,qBAAiB,KAAK,OAAO,SAAS,OAAO,OAAO,YAAY,KAAK,OAAO,WAAW;IAGvF,MAAM,QAAQ,KAAK,OAAO,iBAAiB,WAAW;AACtD,UAAM,YACH,OAAO,KAAK,OAAO,OAAO,aAAa,CACvC,IAAI;KACH,QAAQ,YAAY;KACpB;KACA,WAAW,mBAAmB,MAAM;KACpC,aAAa,IAAI,KAAK,KAAK,KAAK,GAAG,MAAM;KAC1C,CAAC,CACD,MAAM,GAAG,KAAK,OAAO,OAAO,aAAa,IAAI,MAAM,GAAG,CAAC;;IAG9D;;;;;;AC7LN,MAAa,4BAA4B,IAAI,mBAE1C;AAEH,eAAsB,uBACpB,IACA,IACY;AACZ,QAAO,GAAG,YAAY,OAAO,OAAO;AAClC,SAAO,0BAA0B,IAAI,UAAU,GAAG,GAAG,CAAC;GACtD;;AAGJ,SAAgB,wBAEF;AACZ,cAAa,0BAA0B,UAAU"}
|
|
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
package/src/index.ts
CHANGED
package/src/integration.e2e.ts
CHANGED
|
@@ -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 =
|
|
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<
|
|
18
|
-
getTransaction?: (() => PostgresJsDatabase<
|
|
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
|
|
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<
|
|
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
|
-
|
|
114
|
-
const now = new Date()
|
|
113
|
+
const now = new Date()
|
|
115
114
|
|
|
116
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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 {
|
|
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 {
|