@outbox-event-bus/sqlite-better-sqlite3-outbox 1.0.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 +759 -0
- package/dist/index.cjs +259 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +75 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +75 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +228 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
- package/src/error-handling.test.ts +145 -0
- package/src/index.ts +2 -0
- package/src/integration.e2e.ts +258 -0
- package/src/schema.sql +30 -0
- package/src/sqlite-better-sqlite3-outbox.ts +291 -0
- package/src/sync-transaction.test.ts +117 -0
- package/src/transaction-storage.ts +42 -0
- package/src/transactions.test.ts +196 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
let better_sqlite3 = require("better-sqlite3");
|
|
29
|
+
better_sqlite3 = __toESM(better_sqlite3);
|
|
30
|
+
let outbox_event_bus = require("outbox-event-bus");
|
|
31
|
+
let node_async_hooks = require("node:async_hooks");
|
|
32
|
+
|
|
33
|
+
//#region src/sqlite-better-sqlite3-outbox.ts
|
|
34
|
+
const DEFAULT_EXPIRE_IN_SECONDS = 300;
|
|
35
|
+
const getOutboxSchema = (tableName, archiveTableName) => `
|
|
36
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
type TEXT NOT NULL,
|
|
39
|
+
payload TEXT NOT NULL,
|
|
40
|
+
occurred_at TEXT NOT NULL,
|
|
41
|
+
status TEXT NOT NULL DEFAULT '${outbox_event_bus.EventStatus.CREATED}',
|
|
42
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
43
|
+
last_error TEXT,
|
|
44
|
+
next_retry_at TEXT,
|
|
45
|
+
created_on TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
46
|
+
started_on TEXT,
|
|
47
|
+
completed_on TEXT,
|
|
48
|
+
keep_alive TEXT,
|
|
49
|
+
expire_in_seconds INTEGER NOT NULL DEFAULT ${DEFAULT_EXPIRE_IN_SECONDS}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE IF NOT EXISTS ${archiveTableName} (
|
|
53
|
+
id TEXT PRIMARY KEY,
|
|
54
|
+
type TEXT NOT NULL,
|
|
55
|
+
payload TEXT NOT NULL,
|
|
56
|
+
occurred_at TEXT NOT NULL,
|
|
57
|
+
status TEXT NOT NULL,
|
|
58
|
+
retry_count INTEGER NOT NULL,
|
|
59
|
+
last_error TEXT,
|
|
60
|
+
created_on TEXT NOT NULL,
|
|
61
|
+
started_on TEXT,
|
|
62
|
+
completed_on TEXT NOT NULL
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_${tableName}_status_retry ON ${tableName} (status, next_retry_at);
|
|
66
|
+
`;
|
|
67
|
+
var SqliteBetterSqlite3Outbox = class {
|
|
68
|
+
config;
|
|
69
|
+
db;
|
|
70
|
+
poller;
|
|
71
|
+
constructor(config) {
|
|
72
|
+
if (config.db) this.db = config.db;
|
|
73
|
+
else {
|
|
74
|
+
if (!config.dbPath) throw new Error("dbPath is required if db is not provided");
|
|
75
|
+
this.db = new better_sqlite3.default(config.dbPath);
|
|
76
|
+
this.db.pragma("journal_mode = WAL");
|
|
77
|
+
}
|
|
78
|
+
this.config = {
|
|
79
|
+
batchSize: config.batchSize ?? 50,
|
|
80
|
+
pollIntervalMs: config.pollIntervalMs ?? 1e3,
|
|
81
|
+
maxRetries: config.maxRetries ?? 5,
|
|
82
|
+
baseBackoffMs: config.baseBackoffMs ?? 1e3,
|
|
83
|
+
processingTimeoutMs: config.processingTimeoutMs ?? 3e4,
|
|
84
|
+
maxErrorBackoffMs: config.maxErrorBackoffMs ?? 3e4,
|
|
85
|
+
dbPath: config.dbPath ?? "",
|
|
86
|
+
db: this.db,
|
|
87
|
+
getTransaction: config.getTransaction,
|
|
88
|
+
tableName: config.tableName ?? "outbox_events",
|
|
89
|
+
archiveTableName: config.archiveTableName ?? "outbox_events_archive"
|
|
90
|
+
};
|
|
91
|
+
this.init();
|
|
92
|
+
this.poller = new outbox_event_bus.PollingService({
|
|
93
|
+
pollIntervalMs: this.config.pollIntervalMs,
|
|
94
|
+
baseBackoffMs: this.config.baseBackoffMs,
|
|
95
|
+
maxErrorBackoffMs: this.config.maxErrorBackoffMs,
|
|
96
|
+
processBatch: (handler) => this.processBatch(handler)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
init() {
|
|
100
|
+
this.db.exec(getOutboxSchema(this.config.tableName, this.config.archiveTableName));
|
|
101
|
+
}
|
|
102
|
+
async publish(events, transaction) {
|
|
103
|
+
if (events.length === 0) return;
|
|
104
|
+
const executor = transaction ?? this.config.getTransaction?.() ?? this.db;
|
|
105
|
+
const insert = executor.prepare(`
|
|
106
|
+
INSERT INTO ${this.config.tableName} (id, type, payload, occurred_at, status)
|
|
107
|
+
VALUES (?, ?, ?, ?, '${outbox_event_bus.EventStatus.CREATED}')
|
|
108
|
+
`);
|
|
109
|
+
executor.transaction(() => {
|
|
110
|
+
for (const event of events) insert.run(event.id, event.type, JSON.stringify(event.payload), event.occurredAt.toISOString());
|
|
111
|
+
})();
|
|
112
|
+
}
|
|
113
|
+
async getFailedEvents() {
|
|
114
|
+
return this.db.prepare(`
|
|
115
|
+
SELECT * FROM ${this.config.tableName}
|
|
116
|
+
WHERE status = '${outbox_event_bus.EventStatus.FAILED}'
|
|
117
|
+
ORDER BY occurred_at DESC
|
|
118
|
+
LIMIT 100
|
|
119
|
+
`).all().map((row) => {
|
|
120
|
+
const event = {
|
|
121
|
+
id: row.id,
|
|
122
|
+
type: row.type,
|
|
123
|
+
payload: JSON.parse(row.payload),
|
|
124
|
+
occurredAt: new Date(row.occurred_at),
|
|
125
|
+
retryCount: row.retry_count
|
|
126
|
+
};
|
|
127
|
+
if (row.last_error) event.error = row.last_error;
|
|
128
|
+
if (row.started_on) event.lastAttemptAt = new Date(row.started_on);
|
|
129
|
+
return event;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async retryEvents(eventIds) {
|
|
133
|
+
if (eventIds.length === 0) return;
|
|
134
|
+
const placeholders = eventIds.map(() => "?").join(",");
|
|
135
|
+
this.db.prepare(`
|
|
136
|
+
UPDATE ${this.config.tableName}
|
|
137
|
+
SET status = '${outbox_event_bus.EventStatus.CREATED}',
|
|
138
|
+
retry_count = 0,
|
|
139
|
+
next_retry_at = NULL,
|
|
140
|
+
last_error = NULL
|
|
141
|
+
WHERE id IN (${placeholders})
|
|
142
|
+
`).run(...eventIds);
|
|
143
|
+
}
|
|
144
|
+
start(handler, onError) {
|
|
145
|
+
this.poller.start(handler, onError);
|
|
146
|
+
}
|
|
147
|
+
async stop() {
|
|
148
|
+
await this.poller.stop();
|
|
149
|
+
}
|
|
150
|
+
async processBatch(handler) {
|
|
151
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
152
|
+
const msNow = Date.now();
|
|
153
|
+
const lockedEvents = this.db.transaction(() => {
|
|
154
|
+
const rows = this.db.prepare(`
|
|
155
|
+
SELECT * FROM ${this.config.tableName}
|
|
156
|
+
WHERE status = '${outbox_event_bus.EventStatus.CREATED}'
|
|
157
|
+
OR (status = '${outbox_event_bus.EventStatus.FAILED}' AND retry_count < ? AND next_retry_at <= ?)
|
|
158
|
+
OR (status = '${outbox_event_bus.EventStatus.ACTIVE}' AND datetime(keep_alive, '+' || expire_in_seconds || ' seconds') < datetime(?))
|
|
159
|
+
LIMIT ?
|
|
160
|
+
`).all(this.config.maxRetries, now, now, this.config.batchSize);
|
|
161
|
+
if (rows.length === 0) return [];
|
|
162
|
+
const ids = rows.map((r) => r.id);
|
|
163
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
164
|
+
this.db.prepare(`
|
|
165
|
+
UPDATE ${this.config.tableName}
|
|
166
|
+
SET status = '${outbox_event_bus.EventStatus.ACTIVE}',
|
|
167
|
+
started_on = ?,
|
|
168
|
+
keep_alive = ?
|
|
169
|
+
WHERE id IN (${placeholders})
|
|
170
|
+
`).run(now, now, ...ids);
|
|
171
|
+
return rows;
|
|
172
|
+
})();
|
|
173
|
+
if (lockedEvents.length === 0) return;
|
|
174
|
+
const busEvents = lockedEvents.map((row) => ({
|
|
175
|
+
id: row.id,
|
|
176
|
+
type: row.type,
|
|
177
|
+
payload: JSON.parse(row.payload),
|
|
178
|
+
occurredAt: new Date(row.occurred_at)
|
|
179
|
+
}));
|
|
180
|
+
const completedEvents = [];
|
|
181
|
+
for (let index = 0; index < busEvents.length; index++) {
|
|
182
|
+
const event = busEvents[index];
|
|
183
|
+
const lockedEvent = lockedEvents[index];
|
|
184
|
+
try {
|
|
185
|
+
await handler(event);
|
|
186
|
+
completedEvents.push({
|
|
187
|
+
event,
|
|
188
|
+
lockedEvent
|
|
189
|
+
});
|
|
190
|
+
} catch (error) {
|
|
191
|
+
const retryCount = lockedEvent.retry_count + 1;
|
|
192
|
+
(0, outbox_event_bus.reportEventError)(this.poller.onError, error, event, retryCount, this.config.maxRetries);
|
|
193
|
+
const delay = this.poller.calculateBackoff(retryCount);
|
|
194
|
+
this.db.prepare(`
|
|
195
|
+
UPDATE ${this.config.tableName}
|
|
196
|
+
SET status = '${outbox_event_bus.EventStatus.FAILED}',
|
|
197
|
+
retry_count = ?,
|
|
198
|
+
last_error = ?,
|
|
199
|
+
next_retry_at = ?
|
|
200
|
+
WHERE id = ?
|
|
201
|
+
`).run(retryCount, (0, outbox_event_bus.formatErrorMessage)(error), new Date(msNow + delay).toISOString(), lockedEvent.id);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (completedEvents.length > 0) this.db.transaction(() => {
|
|
205
|
+
const insertArchive = this.db.prepare(`
|
|
206
|
+
INSERT INTO ${this.config.archiveTableName} (
|
|
207
|
+
id, type, payload, occurred_at, status, retry_count, last_error, created_on, started_on, completed_on
|
|
208
|
+
) VALUES (?, ?, ?, ?, '${outbox_event_bus.EventStatus.COMPLETED}', ?, ?, ?, ?, ?)
|
|
209
|
+
`);
|
|
210
|
+
const deleteEvent = this.db.prepare(`DELETE FROM ${this.config.tableName} WHERE id = ?`);
|
|
211
|
+
const completionTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
212
|
+
for (const { lockedEvent } of completedEvents) {
|
|
213
|
+
insertArchive.run(lockedEvent.id, lockedEvent.type, lockedEvent.payload, lockedEvent.occurred_at, lockedEvent.retry_count, lockedEvent.last_error, lockedEvent.created_on, now, completionTime);
|
|
214
|
+
deleteEvent.run(lockedEvent.id);
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/transaction-storage.ts
|
|
222
|
+
const betterSqlite3TransactionStorage = new node_async_hooks.AsyncLocalStorage();
|
|
223
|
+
async function withBetterSqlite3Transaction(db, fn) {
|
|
224
|
+
return betterSqlite3TransactionStorage.run(db, async () => {
|
|
225
|
+
if (db.inTransaction) {
|
|
226
|
+
const savepointName = `sp_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
227
|
+
db.prepare(`SAVEPOINT ${savepointName}`).run();
|
|
228
|
+
try {
|
|
229
|
+
const result = await fn(db);
|
|
230
|
+
db.prepare(`RELEASE ${savepointName}`).run();
|
|
231
|
+
return result;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
db.prepare(`ROLLBACK TO ${savepointName}`).run();
|
|
234
|
+
db.prepare(`RELEASE ${savepointName}`).run();
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
db.prepare("BEGIN").run();
|
|
239
|
+
try {
|
|
240
|
+
const result = await fn(db);
|
|
241
|
+
db.prepare("COMMIT").run();
|
|
242
|
+
return result;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
db.prepare("ROLLBACK").run();
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function getBetterSqlite3Transaction() {
|
|
251
|
+
return () => betterSqlite3TransactionStorage.getStore();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
//#endregion
|
|
255
|
+
exports.SqliteBetterSqlite3Outbox = SqliteBetterSqlite3Outbox;
|
|
256
|
+
exports.betterSqlite3TransactionStorage = betterSqlite3TransactionStorage;
|
|
257
|
+
exports.getBetterSqlite3Transaction = getBetterSqlite3Transaction;
|
|
258
|
+
exports.withBetterSqlite3Transaction = withBetterSqlite3Transaction;
|
|
259
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["EventStatus","Database","PollingService","event: FailedBusEvent","busEvents: BusEvent[]","completedEvents: { event: BusEvent; lockedEvent: OutboxRow }[]","betterSqlite3TransactionStorage: AsyncLocalStorage<Database>","AsyncLocalStorage"],"sources":["../src/sqlite-better-sqlite3-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":["import Database from \"better-sqlite3\"\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\"\n\nconst DEFAULT_EXPIRE_IN_SECONDS = 300\n\nconst getOutboxSchema = (tableName: string, archiveTableName: string) => `\n CREATE TABLE IF NOT EXISTS ${tableName} (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n payload TEXT NOT NULL,\n occurred_at TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT '${EventStatus.CREATED}',\n retry_count INTEGER NOT NULL DEFAULT 0,\n last_error TEXT,\n next_retry_at TEXT,\n created_on TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),\n started_on TEXT,\n completed_on TEXT,\n keep_alive TEXT,\n expire_in_seconds INTEGER NOT NULL DEFAULT ${DEFAULT_EXPIRE_IN_SECONDS}\n );\n\n CREATE TABLE IF NOT EXISTS ${archiveTableName} (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n payload TEXT NOT NULL,\n occurred_at TEXT NOT NULL,\n status TEXT NOT NULL,\n retry_count INTEGER NOT NULL,\n last_error TEXT,\n created_on TEXT NOT NULL,\n started_on TEXT,\n completed_on TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_${tableName}_status_retry ON ${tableName} (status, next_retry_at);\n`\n\nexport interface SqliteBetterSqlite3OutboxConfig extends OutboxConfig {\n dbPath?: string\n db?: Database.Database\n getTransaction?: (() => Database.Database | undefined) | undefined\n tableName?: string\n archiveTableName?: string\n}\n\ninterface OutboxRow {\n id: string\n type: string\n payload: string\n occurred_at: string\n status: EventStatus\n retry_count: number\n next_retry_at: string | null\n last_error: string | null\n created_on: string\n started_on: string | null\n completed_on: string | null\n keep_alive: string | null\n expire_in_seconds: number\n}\n\nexport class SqliteBetterSqlite3Outbox implements IOutbox<Database.Database> {\n private readonly config: Required<SqliteBetterSqlite3OutboxConfig>\n private readonly db: Database.Database\n private readonly poller: PollingService\n\n constructor(config: SqliteBetterSqlite3OutboxConfig) {\n if (config.db) {\n this.db = config.db\n } else {\n if (!config.dbPath) throw new Error(\"dbPath is required if db is not provided\")\n this.db = new Database(config.dbPath)\n this.db.pragma(\"journal_mode = WAL\")\n }\n\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 dbPath: config.dbPath ?? \"\",\n db: this.db,\n getTransaction: config.getTransaction,\n tableName: config.tableName ?? \"outbox_events\",\n archiveTableName: config.archiveTableName ?? \"outbox_events_archive\",\n } as Required<SqliteBetterSqlite3OutboxConfig>\n\n this.init()\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 private init() {\n this.db.exec(getOutboxSchema(this.config.tableName, this.config.archiveTableName))\n }\n\n async publish(events: BusEvent[], transaction?: Database.Database): Promise<void> {\n if (events.length === 0) return\n\n const executor = transaction ?? this.config.getTransaction?.() ?? this.db\n\n const insert = executor.prepare(`\n INSERT INTO ${this.config.tableName} (id, type, payload, occurred_at, status)\n VALUES (?, ?, ?, ?, '${EventStatus.CREATED}')\n `)\n\n executor.transaction(() => {\n for (const event of events) {\n insert.run(\n event.id,\n event.type,\n JSON.stringify(event.payload),\n event.occurredAt.toISOString()\n )\n }\n })()\n }\n\n async getFailedEvents(): Promise<FailedBusEvent[]> {\n const rows = this.db\n .prepare(`\n SELECT * FROM ${this.config.tableName}\n WHERE status = '${EventStatus.FAILED}'\n ORDER BY occurred_at DESC\n LIMIT 100\n `)\n .all() as OutboxRow[]\n\n return rows.map((row) => {\n const event: FailedBusEvent = {\n id: row.id,\n type: row.type,\n payload: JSON.parse(row.payload),\n occurredAt: new Date(row.occurred_at),\n retryCount: row.retry_count,\n }\n if (row.last_error) event.error = row.last_error\n if (row.started_on) event.lastAttemptAt = new Date(row.started_on)\n return event\n })\n }\n\n async retryEvents(eventIds: string[]): Promise<void> {\n if (eventIds.length === 0) return\n\n const placeholders = eventIds.map(() => \"?\").join(\",\")\n this.db\n .prepare(`\n UPDATE ${this.config.tableName}\n SET status = '${EventStatus.CREATED}',\n retry_count = 0,\n next_retry_at = NULL,\n last_error = NULL\n WHERE id IN (${placeholders})\n `)\n .run(...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().toISOString()\n const msNow = Date.now()\n\n const lockedEvents = this.db.transaction(() => {\n // Select events that are ready to process:\n // 1. New events (status = 'created')\n // 2. Failed events that can be retried (retry_count < max AND next_retry_at has passed)\n // 3. Stuck events (status = 'active' but keepAlive + expire_in_seconds < now)\n const rows = this.db\n .prepare(`\n SELECT * FROM ${this.config.tableName}\n WHERE status = '${EventStatus.CREATED}'\n OR (status = '${EventStatus.FAILED}' AND retry_count < ? AND next_retry_at <= ?)\n OR (status = '${EventStatus.ACTIVE}' AND datetime(keep_alive, '+' || expire_in_seconds || ' seconds') < datetime(?))\n LIMIT ?\n `)\n .all(this.config.maxRetries, now, now, this.config.batchSize) as OutboxRow[]\n\n if (rows.length === 0) return []\n\n const ids = rows.map((r) => r.id)\n const placeholders = ids.map(() => \"?\").join(\",\")\n\n this.db\n .prepare(`\n UPDATE ${this.config.tableName}\n SET status = '${EventStatus.ACTIVE}',\n started_on = ?,\n keep_alive = ?\n WHERE id IN (${placeholders})\n `)\n .run(now, now, ...ids)\n\n return rows\n })()\n\n if (lockedEvents.length === 0) return\n\n const busEvents: BusEvent[] = lockedEvents.map((row) => ({\n id: row.id,\n type: row.type,\n payload: JSON.parse(row.payload),\n occurredAt: new Date(row.occurred_at),\n }))\n\n const completedEvents: { event: BusEvent; lockedEvent: OutboxRow }[] = []\n\n for (let index = 0; index < busEvents.length; index++) {\n const event = busEvents[index]!\n const lockedEvent = lockedEvents[index]!\n\n try {\n await handler(event)\n completedEvents.push({ event, lockedEvent })\n } catch (error) {\n const retryCount = lockedEvent.retry_count + 1\n reportEventError(this.poller.onError, error, event, retryCount, this.config.maxRetries)\n\n const delay = this.poller.calculateBackoff(retryCount)\n\n this.db\n .prepare(`\n UPDATE ${this.config.tableName}\n SET status = '${EventStatus.FAILED}',\n retry_count = ?,\n last_error = ?,\n next_retry_at = ?\n WHERE id = ?\n `)\n .run(\n retryCount,\n formatErrorMessage(error),\n new Date(msNow + delay).toISOString(),\n lockedEvent.id\n )\n }\n }\n\n if (completedEvents.length > 0) {\n this.db.transaction(() => {\n const insertArchive = this.db.prepare(`\n INSERT INTO ${this.config.archiveTableName} (\n id, type, payload, occurred_at, status, retry_count, last_error, created_on, started_on, completed_on\n ) VALUES (?, ?, ?, ?, '${EventStatus.COMPLETED}', ?, ?, ?, ?, ?)\n `)\n const deleteEvent = this.db.prepare(`DELETE FROM ${this.config.tableName} WHERE id = ?`)\n\n const completionTime = new Date().toISOString()\n for (const { lockedEvent } of completedEvents) {\n insertArchive.run(\n lockedEvent.id,\n lockedEvent.type,\n lockedEvent.payload,\n lockedEvent.occurred_at,\n lockedEvent.retry_count,\n lockedEvent.last_error,\n lockedEvent.created_on,\n now,\n completionTime\n )\n deleteEvent.run(lockedEvent.id)\n }\n })()\n }\n }\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\"\nimport type { Database } from \"better-sqlite3\"\n\nexport type { Database }\n\nexport const betterSqlite3TransactionStorage: AsyncLocalStorage<Database> =\n new AsyncLocalStorage<Database>()\n\nexport async function withBetterSqlite3Transaction<T>(\n db: Database,\n fn: (tx: Database) => Promise<T>\n): Promise<T> {\n return betterSqlite3TransactionStorage.run(db, async () => {\n if (db.inTransaction) {\n const savepointName = `sp_${Date.now()}_${Math.random().toString(36).slice(2)}`\n db.prepare(`SAVEPOINT ${savepointName}`).run()\n try {\n const result = await fn(db)\n db.prepare(`RELEASE ${savepointName}`).run()\n return result\n } catch (error) {\n db.prepare(`ROLLBACK TO ${savepointName}`).run()\n db.prepare(`RELEASE ${savepointName}`).run()\n throw error\n }\n } else {\n db.prepare(\"BEGIN\").run()\n try {\n const result = await fn(db)\n db.prepare(\"COMMIT\").run()\n return result\n } catch (error) {\n db.prepare(\"ROLLBACK\").run()\n throw error\n }\n }\n })\n}\n\nexport function getBetterSqlite3Transaction(): () => Database | undefined {\n return () => betterSqlite3TransactionStorage.getStore()\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Database$1, { Database } from "better-sqlite3";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
|
|
4
|
+
//#region ../../core/src/errors/errors.d.ts
|
|
5
|
+
interface OutboxErrorContext {
|
|
6
|
+
event?: BusEvent | FailedBusEvent;
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
declare abstract class OutboxError<TContext extends OutboxErrorContext = OutboxErrorContext> extends Error {
|
|
11
|
+
context?: TContext | undefined;
|
|
12
|
+
constructor(message: string, name: string, context?: TContext);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region ../../core/src/types/types.d.ts
|
|
16
|
+
type BusEvent<T extends string = string, P = unknown> = {
|
|
17
|
+
id: string;
|
|
18
|
+
type: T;
|
|
19
|
+
payload: P;
|
|
20
|
+
occurredAt: Date;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
};
|
|
23
|
+
type FailedBusEvent<T extends string = string, P = unknown> = BusEvent<T, P> & {
|
|
24
|
+
error?: string;
|
|
25
|
+
retryCount: number;
|
|
26
|
+
lastAttemptAt?: Date;
|
|
27
|
+
};
|
|
28
|
+
type ErrorHandler = (error: OutboxError) => void;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region ../../core/src/types/interfaces.d.ts
|
|
31
|
+
interface IOutbox<TTransaction> {
|
|
32
|
+
publish: (events: BusEvent[], transaction?: TTransaction) => Promise<void>;
|
|
33
|
+
start: (handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler) => void;
|
|
34
|
+
stop: () => Promise<void>;
|
|
35
|
+
getFailedEvents: () => Promise<FailedBusEvent[]>;
|
|
36
|
+
retryEvents: (eventIds: string[]) => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
interface OutboxConfig {
|
|
39
|
+
maxRetries?: number;
|
|
40
|
+
baseBackoffMs?: number;
|
|
41
|
+
pollIntervalMs?: number;
|
|
42
|
+
batchSize?: number;
|
|
43
|
+
processingTimeoutMs?: number;
|
|
44
|
+
maxErrorBackoffMs?: number;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/sqlite-better-sqlite3-outbox.d.ts
|
|
48
|
+
interface SqliteBetterSqlite3OutboxConfig extends OutboxConfig {
|
|
49
|
+
dbPath?: string;
|
|
50
|
+
db?: Database$1.Database;
|
|
51
|
+
getTransaction?: (() => Database$1.Database | undefined) | undefined;
|
|
52
|
+
tableName?: string;
|
|
53
|
+
archiveTableName?: string;
|
|
54
|
+
}
|
|
55
|
+
declare class SqliteBetterSqlite3Outbox implements IOutbox<Database$1.Database> {
|
|
56
|
+
private readonly config;
|
|
57
|
+
private readonly db;
|
|
58
|
+
private readonly poller;
|
|
59
|
+
constructor(config: SqliteBetterSqlite3OutboxConfig);
|
|
60
|
+
private init;
|
|
61
|
+
publish(events: BusEvent[], transaction?: Database$1.Database): Promise<void>;
|
|
62
|
+
getFailedEvents(): Promise<FailedBusEvent[]>;
|
|
63
|
+
retryEvents(eventIds: string[]): Promise<void>;
|
|
64
|
+
start(handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler): void;
|
|
65
|
+
stop(): Promise<void>;
|
|
66
|
+
private processBatch;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/transaction-storage.d.ts
|
|
70
|
+
declare const betterSqlite3TransactionStorage: AsyncLocalStorage<Database>;
|
|
71
|
+
declare function withBetterSqlite3Transaction<T>(db: Database, fn: (tx: Database) => Promise<T>): Promise<T>;
|
|
72
|
+
declare function getBetterSqlite3Transaction(): () => Database | undefined;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { type Database, SqliteBetterSqlite3Outbox, SqliteBetterSqlite3OutboxConfig, betterSqlite3TransactionStorage, getBetterSqlite3Transaction, withBetterSqlite3Transaction };
|
|
75
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +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/sqlite-better-sqlite3-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":[],"mappings":";;;;UAEiB,kBAAA;UACP,WAAW;;EADJ,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAkB;AAMnC;AACmB,uBADG,WACH,CAAA,iBAAA,kBAAA,GAAqB,kBAArB,CAAA,SACT,KAAA,CADS;EAAqB,OAAA,CAAA,EAErB,QAFqB,GAAA,SAAA;EAErB,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAEoC,QAFpC;;;;KCPP;;EDFK,IAAA,ECIT,CDJS;EAMK,OAAA,ECDX,CDCW;EACH,UAAA,ECDL,IDCK;EAAqB,QAAA,CAAA,ECA3B,MDA2B,CAAA,MAAA,EAAA,OAAA,CAAA;CAErB;AAEoC,KCD3C,cDC2C,CAAA,UAAA,MAAA,GAAA,MAAA,EAAA,IAAA,OAAA,CAAA,GCDc,QDCd,CCDuB,CDCvB,ECD0B,CDC1B,CAAA,GAAA;EAH7C,KAAA,CAAA,EAAA,MAAA;EAAK,UAAA,EAAA,MAAA;kBCKG;;AARP,KAsBC,YAAA,GAtBD,CAAA,KAAA,EAsBwB,WAtBxB,EAAA,GAAA,IAAA;;;UCLM;oBACG,0BAA0B,iBAAiB;2BACpC,aAAa,wBAAwB;EFF/C,IAAA,EAAA,GAAA,GEGH,OFHG,CAAA,IAAkB,CAAA;EAMb,eAAW,EAAA,GAAA,GEFR,OFEQ,CEFA,cFEA,EAAA,CAAA;EACd,WAAA,EAAA,CAAA,QAAA,EAAA,MAAA,EAAA,EAAA,GEFoB,OFEpB,CAAA,IAAA,CAAA;;AEPF,UA4DA,YAAA,CA5DO;EACJ,UAAA,CAAA,EAAA,MAAA;EAA0B,aAAA,CAAA,EAAA,MAAA;EAAiB,cAAA,CAAA,EAAA,MAAA;EACpC,SAAA,CAAA,EAAA,MAAA;EAAa,mBAAA,CAAA,EAAA,MAAA;EAAwB,iBAAA,CAAA,EAAA,MAAA;;;;UC4C/C,+BAAA,SAAwC;;EH9CxC,EAAA,CAAA,EGgDV,UAAA,CAAS,QHhDmB;EAMb,cAAW,CAAA,EAAA,CAAA,GAAA,GG2CP,UAAA,CAAS,QH3CF,GAAA,SAAA,CAAA,GAAA,SAAA;EACd,SAAA,CAAA,EAAA,MAAA;EAAqB,gBAAA,CAAA,EAAA,MAAA;;AAIe,cG2D1C,yBAAA,YAAqC,OH3DK,CG2DG,UAAA,CAAS,QH3DZ,CAAA,CAAA;EAH7C,iBAAA,MAAA;EAAK,iBAAA,EAAA;;sBGmEO;;EFzEV,OAAA,CAAA,MAAQ,EE8GI,QF9GJ,EAAA,EAAA,WAAA,CAAA,EE8G8B,UAAA,CAAS,QF9GvC,CAAA,EE8GkD,OF9GlD,CAAA,IAAA,CAAA;EAEZ,eAAA,CAAA,CAAA,EEkImB,OFlInB,CEkI2B,cFlI3B,EAAA,CAAA;EACG,WAAA,CAAA,QAAA,EAAA,MAAA,EAAA,CAAA,EEyJ8B,OFzJ9B,CAAA,IAAA,CAAA;EACG,KAAA,CAAA,OAAA,EAAA,CAAA,KAAA,EEwKW,QFxKX,EAAA,GEwKwB,OFxKxB,CAAA,IAAA,CAAA,EAAA,OAAA,EEwKgD,YFxKhD,CAAA,EAAA,IAAA;EACD,IAAA,CAAA,CAAA,EE2KG,OF3KH,CAAA,IAAA,CAAA;EAAM,QAAA,YAAA;AAGnB;;;cGPa,iCAAiC,kBAAkB;AJH/C,iBIMK,4BJLD,CAAA,CAAA,CAAA,CAAA,EAAA,EIMf,QJN6B,EAAA,EAAA,EAAA,CAAA,EAAA,EIOxB,QJPwB,EAAA,GIOX,OJPW,CIOH,CJPG,CAAA,CAAA,EIQhC,OJRgC,CIQxB,CJRwB,CAAA;AAKb,iBI+BN,2BAAA,CAAA,CJ/BiB,EAAA,GAAA,GI+BoB,QJ/BpB,GAAA,SAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Database$1, { Database } from "better-sqlite3";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
|
|
4
|
+
//#region ../../core/src/errors/errors.d.ts
|
|
5
|
+
interface OutboxErrorContext {
|
|
6
|
+
event?: BusEvent | FailedBusEvent;
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
declare abstract class OutboxError<TContext extends OutboxErrorContext = OutboxErrorContext> extends Error {
|
|
11
|
+
context?: TContext | undefined;
|
|
12
|
+
constructor(message: string, name: string, context?: TContext);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region ../../core/src/types/types.d.ts
|
|
16
|
+
type BusEvent<T extends string = string, P = unknown> = {
|
|
17
|
+
id: string;
|
|
18
|
+
type: T;
|
|
19
|
+
payload: P;
|
|
20
|
+
occurredAt: Date;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
};
|
|
23
|
+
type FailedBusEvent<T extends string = string, P = unknown> = BusEvent<T, P> & {
|
|
24
|
+
error?: string;
|
|
25
|
+
retryCount: number;
|
|
26
|
+
lastAttemptAt?: Date;
|
|
27
|
+
};
|
|
28
|
+
type ErrorHandler = (error: OutboxError) => void;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region ../../core/src/types/interfaces.d.ts
|
|
31
|
+
interface IOutbox<TTransaction> {
|
|
32
|
+
publish: (events: BusEvent[], transaction?: TTransaction) => Promise<void>;
|
|
33
|
+
start: (handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler) => void;
|
|
34
|
+
stop: () => Promise<void>;
|
|
35
|
+
getFailedEvents: () => Promise<FailedBusEvent[]>;
|
|
36
|
+
retryEvents: (eventIds: string[]) => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
interface OutboxConfig {
|
|
39
|
+
maxRetries?: number;
|
|
40
|
+
baseBackoffMs?: number;
|
|
41
|
+
pollIntervalMs?: number;
|
|
42
|
+
batchSize?: number;
|
|
43
|
+
processingTimeoutMs?: number;
|
|
44
|
+
maxErrorBackoffMs?: number;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/sqlite-better-sqlite3-outbox.d.ts
|
|
48
|
+
interface SqliteBetterSqlite3OutboxConfig extends OutboxConfig {
|
|
49
|
+
dbPath?: string;
|
|
50
|
+
db?: Database$1.Database;
|
|
51
|
+
getTransaction?: (() => Database$1.Database | undefined) | undefined;
|
|
52
|
+
tableName?: string;
|
|
53
|
+
archiveTableName?: string;
|
|
54
|
+
}
|
|
55
|
+
declare class SqliteBetterSqlite3Outbox implements IOutbox<Database$1.Database> {
|
|
56
|
+
private readonly config;
|
|
57
|
+
private readonly db;
|
|
58
|
+
private readonly poller;
|
|
59
|
+
constructor(config: SqliteBetterSqlite3OutboxConfig);
|
|
60
|
+
private init;
|
|
61
|
+
publish(events: BusEvent[], transaction?: Database$1.Database): Promise<void>;
|
|
62
|
+
getFailedEvents(): Promise<FailedBusEvent[]>;
|
|
63
|
+
retryEvents(eventIds: string[]): Promise<void>;
|
|
64
|
+
start(handler: (event: BusEvent) => Promise<void>, onError: ErrorHandler): void;
|
|
65
|
+
stop(): Promise<void>;
|
|
66
|
+
private processBatch;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/transaction-storage.d.ts
|
|
70
|
+
declare const betterSqlite3TransactionStorage: AsyncLocalStorage<Database>;
|
|
71
|
+
declare function withBetterSqlite3Transaction<T>(db: Database, fn: (tx: Database) => Promise<T>): Promise<T>;
|
|
72
|
+
declare function getBetterSqlite3Transaction(): () => Database | undefined;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { type Database, SqliteBetterSqlite3Outbox, SqliteBetterSqlite3OutboxConfig, betterSqlite3TransactionStorage, getBetterSqlite3Transaction, withBetterSqlite3Transaction };
|
|
75
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +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/sqlite-better-sqlite3-outbox.ts","../src/transaction-storage.ts"],"sourcesContent":[],"mappings":";;;;UAEiB,kBAAA;UACP,WAAW;;EADJ,CAAA,GAAA,EAAA,MAAA,CAAA,EAAA,OAAkB;AAMnC;AACmB,uBADG,WACH,CAAA,iBAAA,kBAAA,GAAqB,kBAArB,CAAA,SACT,KAAA,CADS;EAAqB,OAAA,CAAA,EAErB,QAFqB,GAAA,SAAA;EAErB,WAAA,CAAA,OAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAEoC,QAFpC;;;;KCPP;;EDFK,IAAA,ECIT,CDJS;EAMK,OAAA,ECDX,CDCW;EACH,UAAA,ECDL,IDCK;EAAqB,QAAA,CAAA,ECA3B,MDA2B,CAAA,MAAA,EAAA,OAAA,CAAA;CAErB;AAEoC,KCD3C,cDC2C,CAAA,UAAA,MAAA,GAAA,MAAA,EAAA,IAAA,OAAA,CAAA,GCDc,QDCd,CCDuB,CDCvB,ECD0B,CDC1B,CAAA,GAAA;EAH7C,KAAA,CAAA,EAAA,MAAA;EAAK,UAAA,EAAA,MAAA;kBCKG;;AARP,KAsBC,YAAA,GAtBD,CAAA,KAAA,EAsBwB,WAtBxB,EAAA,GAAA,IAAA;;;UCLM;oBACG,0BAA0B,iBAAiB;2BACpC,aAAa,wBAAwB;EFF/C,IAAA,EAAA,GAAA,GEGH,OFHG,CAAA,IAAkB,CAAA;EAMb,eAAW,EAAA,GAAA,GEFR,OFEQ,CEFA,cFEA,EAAA,CAAA;EACd,WAAA,EAAA,CAAA,QAAA,EAAA,MAAA,EAAA,EAAA,GEFoB,OFEpB,CAAA,IAAA,CAAA;;AEPF,UA4DA,YAAA,CA5DO;EACJ,UAAA,CAAA,EAAA,MAAA;EAA0B,aAAA,CAAA,EAAA,MAAA;EAAiB,cAAA,CAAA,EAAA,MAAA;EACpC,SAAA,CAAA,EAAA,MAAA;EAAa,mBAAA,CAAA,EAAA,MAAA;EAAwB,iBAAA,CAAA,EAAA,MAAA;;;;UC4C/C,+BAAA,SAAwC;;EH9CxC,EAAA,CAAA,EGgDV,UAAA,CAAS,QHhDmB;EAMb,cAAW,CAAA,EAAA,CAAA,GAAA,GG2CP,UAAA,CAAS,QH3CF,GAAA,SAAA,CAAA,GAAA,SAAA;EACd,SAAA,CAAA,EAAA,MAAA;EAAqB,gBAAA,CAAA,EAAA,MAAA;;AAIe,cG2D1C,yBAAA,YAAqC,OH3DK,CG2DG,UAAA,CAAS,QH3DZ,CAAA,CAAA;EAH7C,iBAAA,MAAA;EAAK,iBAAA,EAAA;;sBGmEO;;EFzEV,OAAA,CAAA,MAAQ,EE8GI,QF9GJ,EAAA,EAAA,WAAA,CAAA,EE8G8B,UAAA,CAAS,QF9GvC,CAAA,EE8GkD,OF9GlD,CAAA,IAAA,CAAA;EAEZ,eAAA,CAAA,CAAA,EEkImB,OFlInB,CEkI2B,cFlI3B,EAAA,CAAA;EACG,WAAA,CAAA,QAAA,EAAA,MAAA,EAAA,CAAA,EEyJ8B,OFzJ9B,CAAA,IAAA,CAAA;EACG,KAAA,CAAA,OAAA,EAAA,CAAA,KAAA,EEwKW,QFxKX,EAAA,GEwKwB,OFxKxB,CAAA,IAAA,CAAA,EAAA,OAAA,EEwKgD,YFxKhD,CAAA,EAAA,IAAA;EACD,IAAA,CAAA,CAAA,EE2KG,OF3KH,CAAA,IAAA,CAAA;EAAM,QAAA,YAAA;AAGnB;;;cGPa,iCAAiC,kBAAkB;AJH/C,iBIMK,4BJLD,CAAA,CAAA,CAAA,CAAA,EAAA,EIMf,QJN6B,EAAA,EAAA,EAAA,CAAA,EAAA,EIOxB,QJPwB,EAAA,GIOX,OJPW,CIOH,CJPG,CAAA,CAAA,EIQhC,OJRgC,CIQxB,CJRwB,CAAA;AAKb,iBI+BN,2BAAA,CAAA,CJ/BiB,EAAA,GAAA,GI+BoB,QJ/BpB,GAAA,SAAA"}
|