@ddd-ts-kit/knex 0.0.0-stage → 0.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 +545 -2
- package/dist/columns/audit-columns.d.ts +7 -0
- package/dist/columns/audit-columns.d.ts.map +1 -0
- package/dist/columns/audit-columns.js +13 -0
- package/dist/columns/audit-columns.js.map +1 -0
- package/dist/columns/audit-row.d.ts +16 -0
- package/dist/columns/audit-row.d.ts.map +1 -0
- package/dist/columns/audit-row.js +5 -0
- package/dist/columns/audit-row.js.map +1 -0
- package/dist/columns/indexable-length.d.ts +3 -0
- package/dist/columns/indexable-length.d.ts.map +1 -0
- package/dist/columns/indexable-length.js +3 -0
- package/dist/columns/indexable-length.js.map +1 -0
- package/dist/columns/instant.d.ts +6 -0
- package/dist/columns/instant.d.ts.map +1 -0
- package/dist/columns/instant.js +3 -0
- package/dist/columns/instant.js.map +1 -0
- package/dist/columns/tenant-column.d.ts +4 -0
- package/dist/columns/tenant-column.d.ts.map +1 -0
- package/dist/columns/tenant-column.js +6 -0
- package/dist/columns/tenant-column.js.map +1 -0
- package/dist/columns/tenant-row.d.ts +7 -0
- package/dist/columns/tenant-row.d.ts.map +1 -0
- package/dist/columns/tenant-row.js +5 -0
- package/dist/columns/tenant-row.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/outbox/builder-in.d.ts +3 -0
- package/dist/outbox/builder-in.d.ts.map +1 -0
- package/dist/outbox/builder-in.js +4 -0
- package/dist/outbox/builder-in.js.map +1 -0
- package/dist/outbox/create-outbox-tables.d.ts +9 -0
- package/dist/outbox/create-outbox-tables.d.ts.map +1 -0
- package/dist/outbox/create-outbox-tables.js +97 -0
- package/dist/outbox/create-outbox-tables.js.map +1 -0
- package/dist/outbox/drop-outbox-tables.d.ts +5 -0
- package/dist/outbox/drop-outbox-tables.d.ts.map +1 -0
- package/dist/outbox/drop-outbox-tables.js +8 -0
- package/dist/outbox/drop-outbox-tables.js.map +1 -0
- package/dist/outbox/knex-outbox-store.d.ts +61 -0
- package/dist/outbox/knex-outbox-store.d.ts.map +1 -0
- package/dist/outbox/knex-outbox-store.js +184 -0
- package/dist/outbox/knex-outbox-store.js.map +1 -0
- package/dist/outbox/knex-processed-event-store.d.ts +21 -0
- package/dist/outbox/knex-processed-event-store.d.ts.map +1 -0
- package/dist/outbox/knex-processed-event-store.js +26 -0
- package/dist/outbox/knex-processed-event-store.js.map +1 -0
- package/dist/outbox/outbox-tables-options.d.ts +12 -0
- package/dist/outbox/outbox-tables-options.d.ts.map +1 -0
- package/dist/outbox/outbox-tables-options.js +2 -0
- package/dist/outbox/outbox-tables-options.js.map +1 -0
- package/dist/outbox/table-names.d.ts +3 -0
- package/dist/outbox/table-names.d.ts.map +1 -0
- package/dist/outbox/table-names.js +3 -0
- package/dist/outbox/table-names.js.map +1 -0
- package/dist/read-model/column-of.d.ts +3 -0
- package/dist/read-model/column-of.d.ts.map +1 -0
- package/dist/read-model/column-of.js +9 -0
- package/dist/read-model/column-of.js.map +1 -0
- package/dist/read-model/criteria-to-knex.d.ts +11 -0
- package/dist/read-model/criteria-to-knex.d.ts.map +1 -0
- package/dist/read-model/criteria-to-knex.js +28 -0
- package/dist/read-model/criteria-to-knex.js.map +1 -0
- package/dist/read-model/knex-read-model-options.d.ts +10 -0
- package/dist/read-model/knex-read-model-options.d.ts.map +1 -0
- package/dist/read-model/knex-read-model-options.js +2 -0
- package/dist/read-model/knex-read-model-options.js.map +1 -0
- package/dist/read-model/knex-read-model-store.d.ts +16 -0
- package/dist/read-model/knex-read-model-store.d.ts.map +1 -0
- package/dist/read-model/knex-read-model-store.js +13 -0
- package/dist/read-model/knex-read-model-store.js.map +1 -0
- package/dist/read-model/knex-read-model-table.d.ts +32 -0
- package/dist/read-model/knex-read-model-table.d.ts.map +1 -0
- package/dist/read-model/knex-read-model-table.js +83 -0
- package/dist/read-model/knex-read-model-table.js.map +1 -0
- package/dist/read-model/knex-stamped-rows.d.ts +19 -0
- package/dist/read-model/knex-stamped-rows.d.ts.map +1 -0
- package/dist/read-model/knex-stamped-rows.js +68 -0
- package/dist/read-model/knex-stamped-rows.js.map +1 -0
- package/dist/read-model/like-pattern.d.ts +4 -0
- package/dist/read-model/like-pattern.d.ts.map +1 -0
- package/dist/read-model/like-pattern.js +6 -0
- package/dist/read-model/like-pattern.js.map +1 -0
- package/dist/read-model/predicate-to-knex.d.ts +11 -0
- package/dist/read-model/predicate-to-knex.d.ts.map +1 -0
- package/dist/read-model/predicate-to-knex.js +63 -0
- package/dist/read-model/predicate-to-knex.js.map +1 -0
- package/dist/read-model/read-model-columns.d.ts +8 -0
- package/dist/read-model/read-model-columns.d.ts.map +1 -0
- package/dist/read-model/read-model-columns.js +14 -0
- package/dist/read-model/read-model-columns.js.map +1 -0
- package/dist/read-model/records-of.d.ts +3 -0
- package/dist/read-model/records-of.d.ts.map +1 -0
- package/dist/read-model/records-of.js +9 -0
- package/dist/read-model/records-of.js.map +1 -0
- package/dist/read-model/row.mapper.d.ts +8 -0
- package/dist/read-model/row.mapper.d.ts.map +1 -0
- package/dist/read-model/row.mapper.js +17 -0
- package/dist/read-model/row.mapper.js.map +1 -0
- package/dist/sql/dialect-of.d.ts +4 -0
- package/dist/sql/dialect-of.d.ts.map +1 -0
- package/dist/sql/dialect-of.js +17 -0
- package/dist/sql/dialect-of.js.map +1 -0
- package/dist/sql/insert-ignoring-conflict.d.ts +16 -0
- package/dist/sql/insert-ignoring-conflict.d.ts.map +1 -0
- package/dist/sql/insert-ignoring-conflict.js +28 -0
- package/dist/sql/insert-ignoring-conflict.js.map +1 -0
- package/dist/sql/sql-dialect.d.ts +3 -0
- package/dist/sql/sql-dialect.d.ts.map +1 -0
- package/dist/sql/sql-dialect.js +2 -0
- package/dist/sql/sql-dialect.js.map +1 -0
- package/dist/unit-of-work/create-knex-unit-of-work.d.ts +14 -0
- package/dist/unit-of-work/create-knex-unit-of-work.d.ts.map +1 -0
- package/dist/unit-of-work/create-knex-unit-of-work.js +24 -0
- package/dist/unit-of-work/create-knex-unit-of-work.js.map +1 -0
- package/dist/unit-of-work/knex-context.d.ts +13 -0
- package/dist/unit-of-work/knex-context.d.ts.map +1 -0
- package/dist/unit-of-work/knex-context.js +10 -0
- package/dist/unit-of-work/knex-context.js.map +1 -0
- package/package.json +41 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-column.js","sourceRoot":"","sources":["../../src/columns/tenant-column.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,KAA8B;IACzD,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC,WAAW,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TenantScoped } from '@ddd-ts-kit/kernel';
|
|
2
|
+
export interface TenantRow {
|
|
3
|
+
tenant_id: string;
|
|
4
|
+
}
|
|
5
|
+
/** `tenantScopeOf(scope)` under the column name: spread it into an insert and into every `where`. */
|
|
6
|
+
export declare function tenantRow(scoped: TenantScoped): TenantRow;
|
|
7
|
+
//# sourceMappingURL=tenant-row.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-row.d.ts","sourceRoot":"","sources":["../../src/columns/tenant-row.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qGAAqG;AACrG,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAEzD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-row.js","sourceRoot":"","sources":["../../src/columns/tenant-row.ts"],"names":[],"mappings":"AAMA,qGAAqG;AACrG,MAAM,UAAU,SAAS,CAAC,MAAoB;IAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;AACxC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { CONCURRENCY_CONFLICT, ConcurrencyError, optimisticUpdate, schemaNameOf, type VersionStep, } from '@ddd-ts-kit/kernel';
|
|
2
|
+
export * from './unit-of-work/knex-context.js';
|
|
3
|
+
export * from './unit-of-work/create-knex-unit-of-work.js';
|
|
4
|
+
export * from './outbox/table-names.js';
|
|
5
|
+
export * from './outbox/outbox-tables-options.js';
|
|
6
|
+
export * from './outbox/create-outbox-tables.js';
|
|
7
|
+
export * from './outbox/drop-outbox-tables.js';
|
|
8
|
+
export * from './outbox/knex-outbox-store.js';
|
|
9
|
+
export * from './outbox/knex-processed-event-store.js';
|
|
10
|
+
export * from './columns/audit-columns.js';
|
|
11
|
+
export * from './columns/tenant-column.js';
|
|
12
|
+
export * from './columns/audit-row.js';
|
|
13
|
+
export * from './columns/tenant-row.js';
|
|
14
|
+
export * from './sql/insert-ignoring-conflict.js';
|
|
15
|
+
export * from './read-model/read-model-columns.js';
|
|
16
|
+
export * from './read-model/predicate-to-knex.js';
|
|
17
|
+
export * from './read-model/criteria-to-knex.js';
|
|
18
|
+
export * from './read-model/knex-read-model-options.js';
|
|
19
|
+
export * from './read-model/knex-read-model-table.js';
|
|
20
|
+
export * from './read-model/knex-read-model-store.js';
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,KAAK,WAAW,GACjB,MAAM,oBAAoB,CAAC;AAC5B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4CAA4C,CAAC;AAC3D,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wCAAwC,CAAC;AACvD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,kCAAkC,CAAC;AACjD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { CONCURRENCY_CONFLICT, ConcurrencyError, optimisticUpdate, schemaNameOf, } from '@ddd-ts-kit/kernel';
|
|
2
|
+
export * from './unit-of-work/knex-context.js';
|
|
3
|
+
export * from './unit-of-work/create-knex-unit-of-work.js';
|
|
4
|
+
export * from './outbox/table-names.js';
|
|
5
|
+
export * from './outbox/outbox-tables-options.js';
|
|
6
|
+
export * from './outbox/create-outbox-tables.js';
|
|
7
|
+
export * from './outbox/drop-outbox-tables.js';
|
|
8
|
+
export * from './outbox/knex-outbox-store.js';
|
|
9
|
+
export * from './outbox/knex-processed-event-store.js';
|
|
10
|
+
export * from './columns/audit-columns.js';
|
|
11
|
+
export * from './columns/tenant-column.js';
|
|
12
|
+
export * from './columns/audit-row.js';
|
|
13
|
+
export * from './columns/tenant-row.js';
|
|
14
|
+
export * from './sql/insert-ignoring-conflict.js';
|
|
15
|
+
export * from './read-model/read-model-columns.js';
|
|
16
|
+
export * from './read-model/predicate-to-knex.js';
|
|
17
|
+
export * from './read-model/criteria-to-knex.js';
|
|
18
|
+
export * from './read-model/knex-read-model-options.js';
|
|
19
|
+
export * from './read-model/knex-read-model-table.js';
|
|
20
|
+
export * from './read-model/knex-read-model-store.js';
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,GAEb,MAAM,oBAAoB,CAAC;AAC5B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4CAA4C,CAAC;AAC3D,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wCAAwC,CAAC;AACvD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,kCAAkC,CAAC;AACjD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,uCAAuC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder-in.d.ts","sourceRoot":"","sources":["../../src/outbox/builder-in.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,aAAa,CAEpF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder-in.js","sourceRoot":"","sources":["../../src/outbox/builder-in.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS,CAAC,IAAU,EAAE,MAA0B;IAC9D,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAC7E,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
import type { OutboxTablesOptions } from './outbox-tables-options.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the outbox and processed-event tables. Call it from a migration of the consuming
|
|
5
|
+
* project (`export const up = (knex) => createOutboxTables(knex, { schema: 'messaging' })`),
|
|
6
|
+
* which then owns the tables like any other.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createOutboxTables(knex: Knex, options?: OutboxTablesOptions): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=create-outbox-tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-outbox-tables.d.ts","sourceRoot":"","sources":["../../src/outbox/create-outbox-tables.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAMjC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrG"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { INDEXABLE_LENGTH } from '../columns/indexable-length.js';
|
|
2
|
+
import { INSTANT } from '../columns/instant.js';
|
|
3
|
+
import { dialectOf } from '../sql/dialect-of.js';
|
|
4
|
+
import { builderIn } from './builder-in.js';
|
|
5
|
+
import { OUTBOX_TABLE, PROCESSED_EVENTS_TABLE } from './table-names.js';
|
|
6
|
+
/**
|
|
7
|
+
* Creates the outbox and processed-event tables. Call it from a migration of the consuming
|
|
8
|
+
* project (`export const up = (knex) => createOutboxTables(knex, { schema: 'messaging' })`),
|
|
9
|
+
* which then owns the tables like any other.
|
|
10
|
+
*/
|
|
11
|
+
export async function createOutboxTables(knex, options = {}) {
|
|
12
|
+
if (options.schema !== undefined) {
|
|
13
|
+
await createNamespace(knex, options.schema);
|
|
14
|
+
}
|
|
15
|
+
await createOutboxTable(knex, options);
|
|
16
|
+
await createProcessedEventsTable(knex, options);
|
|
17
|
+
}
|
|
18
|
+
/** One namespace per bounded context: a schema in PostgreSQL, a database in MySQL. */
|
|
19
|
+
async function createNamespace(knex, name) {
|
|
20
|
+
switch (dialectOf(knex)) {
|
|
21
|
+
case 'postgres':
|
|
22
|
+
await knex.schema.createSchemaIfNotExists(name);
|
|
23
|
+
return;
|
|
24
|
+
case 'mysql':
|
|
25
|
+
await knex.raw('create database if not exists ??', [name]);
|
|
26
|
+
return;
|
|
27
|
+
case 'sqlite':
|
|
28
|
+
throw new TypeError('SQLite has no schemas: name the tables per context with outboxTable and processedEventsTable');
|
|
29
|
+
default:
|
|
30
|
+
// Any other database: the schema is expected to exist already.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function createOutboxTable(knex, options) {
|
|
34
|
+
const outbox = options.outboxTable ?? OUTBOX_TABLE;
|
|
35
|
+
await builderIn(knex, options.schema).createTable(outbox, (table) => {
|
|
36
|
+
keyColumns(table, dialectOf(knex));
|
|
37
|
+
table.text('type').notNullable();
|
|
38
|
+
table.integer('version').notNullable();
|
|
39
|
+
// Text, not a timestamp: the envelope must come back exactly as published, offset included.
|
|
40
|
+
table.text('occurred_at').notNullable();
|
|
41
|
+
table.jsonb('payload');
|
|
42
|
+
table.text('traceparent');
|
|
43
|
+
table.text('correlation_id');
|
|
44
|
+
table.string('status', 16).notNullable().defaultTo('pending');
|
|
45
|
+
table.integer('attempts').notNullable().defaultTo(0);
|
|
46
|
+
table.datetime('next_attempt_at', INSTANT).notNullable();
|
|
47
|
+
table.text('last_error_code');
|
|
48
|
+
table.datetime('published_at', INSTANT);
|
|
49
|
+
table.datetime('created_at', INSTANT).notNullable().defaultTo(rightNow(knex));
|
|
50
|
+
pendingIndex(table, outbox, knex);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** The identifier and `sequence`, the insertion order that breaks the ties of the claim. */
|
|
54
|
+
function keyColumns(table, dialect) {
|
|
55
|
+
switch (dialect) {
|
|
56
|
+
case 'sqlite':
|
|
57
|
+
// SQLite only auto-increments an integer primary key, so the roles are swapped there.
|
|
58
|
+
table.increments('sequence');
|
|
59
|
+
table.string('id', INDEXABLE_LENGTH).notNullable().unique();
|
|
60
|
+
return;
|
|
61
|
+
case 'mysql':
|
|
62
|
+
table.string('id', INDEXABLE_LENGTH).primary();
|
|
63
|
+
// Spelled out: knex drops AUTO_INCREMENT from a non-primary bigIncrements on MySQL,
|
|
64
|
+
// which also requires such a column to be a key.
|
|
65
|
+
table.specificType('sequence', 'bigint unsigned not null auto_increment unique');
|
|
66
|
+
return;
|
|
67
|
+
default:
|
|
68
|
+
table.string('id', INDEXABLE_LENGTH).primary();
|
|
69
|
+
table.bigIncrements('sequence', { primaryKey: false });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function rightNow(knex) {
|
|
73
|
+
// SQLite knows CURRENT_TIMESTAMP only without a precision.
|
|
74
|
+
return dialectOf(knex) === 'sqlite' ? knex.fn.now() : knex.fn.now(3);
|
|
75
|
+
}
|
|
76
|
+
function pendingIndex(table, outbox, knex) {
|
|
77
|
+
if (dialectOf(knex) === 'mysql') {
|
|
78
|
+
// No partial index in MySQL: the status leads the index instead.
|
|
79
|
+
table.index(['status', 'next_attempt_at', 'sequence'], `${outbox}_pending_idx`);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Partial: the relay only ever scans what is pending, and published rows dominate.
|
|
83
|
+
table.index(['next_attempt_at', 'sequence'], `${outbox}_pending_idx`, {
|
|
84
|
+
predicate: knex.whereRaw("status = 'pending'"),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function createProcessedEventsTable(knex, options) {
|
|
89
|
+
await builderIn(knex, options.schema).createTable(options.processedEventsTable ?? PROCESSED_EVENTS_TABLE, (table) => {
|
|
90
|
+
table.string('consumer', INDEXABLE_LENGTH).notNullable();
|
|
91
|
+
table.string('event_id', INDEXABLE_LENGTH).notNullable();
|
|
92
|
+
table.datetime('processed_at', INSTANT).notNullable().defaultTo(rightNow(knex));
|
|
93
|
+
// The primary key is the unique (consumer, event_id) the store relies on.
|
|
94
|
+
table.primary(['consumer', 'event_id']);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=create-outbox-tables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-outbox-tables.js","sourceRoot":"","sources":["../../src/outbox/create-outbox-tables.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
import type { OutboxTablesOptions } from './outbox-tables-options.js';
|
|
3
|
+
/** The `down` of `createOutboxTables`. The schema itself is left in place. */
|
|
4
|
+
export declare function dropOutboxTables(knex: Knex, options?: OutboxTablesOptions): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=drop-outbox-tables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drop-outbox-tables.d.ts","sourceRoot":"","sources":["../../src/outbox/drop-outbox-tables.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE,8EAA8E;AAC9E,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAInG"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { builderIn } from './builder-in.js';
|
|
2
|
+
import { OUTBOX_TABLE, PROCESSED_EVENTS_TABLE } from './table-names.js';
|
|
3
|
+
/** The `down` of `createOutboxTables`. The schema itself is left in place. */
|
|
4
|
+
export async function dropOutboxTables(knex, options = {}) {
|
|
5
|
+
await builderIn(knex, options.schema).dropTableIfExists(options.processedEventsTable ?? PROCESSED_EVENTS_TABLE);
|
|
6
|
+
await builderIn(knex, options.schema).dropTableIfExists(options.outboxTable ?? OUTBOX_TABLE);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=drop-outbox-tables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drop-outbox-tables.js","sourceRoot":"","sources":["../../src/outbox/drop-outbox-tables.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAExE,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAU,EAAE,OAAO,GAAwB,EAAE;IAClF,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,oBAAoB,IAAI,sBAAsB,CAAC,CAAC;IAEhH,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,WAAW,IAAI,YAAY,CAAC,CAAC;AAC/F,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { IntegrationEvent } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { OutboxRecord, OutboxStats, OutboxStore } from '@ddd-ts-kit/events';
|
|
3
|
+
import type { KnexContext } from '../unit-of-work/knex-context.js';
|
|
4
|
+
/**
|
|
5
|
+
* How concurrent relays share the pending records.
|
|
6
|
+
* - `skip-locked`: one `SELECT ... FOR UPDATE SKIP LOCKED` and one update in a short
|
|
7
|
+
* transaction. Needs PostgreSQL or MySQL 8.
|
|
8
|
+
* - `compare-and-set`: reads the due records, then takes each with an update guarded by the
|
|
9
|
+
* attempt count it read. No lock clause, so it runs on any database, at one statement per
|
|
10
|
+
* record.
|
|
11
|
+
*/
|
|
12
|
+
export type OutboxClaimStrategy = 'skip-locked' | 'compare-and-set';
|
|
13
|
+
export interface KnexOutboxStoreOptions {
|
|
14
|
+
/** The schema the table was created in. Default: the default schema of the connection. */
|
|
15
|
+
readonly schema?: string;
|
|
16
|
+
/** Default `outbox`. */
|
|
17
|
+
readonly table?: string;
|
|
18
|
+
/** How long a claimed record stays invisible to other claims. Default 30000. */
|
|
19
|
+
readonly leaseMs?: number;
|
|
20
|
+
/** Default: `skip-locked` on PostgreSQL and MySQL, `compare-and-set` elsewhere. */
|
|
21
|
+
readonly claim?: OutboxClaimStrategy;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The outbox in a SQL database. `append` runs on the executor of the context: called inside a
|
|
25
|
+
* unit of work, the events are written by the transaction of the command and share its fate,
|
|
26
|
+
* which is the whole point of an outbox.
|
|
27
|
+
*/
|
|
28
|
+
export declare class KnexOutboxStore implements OutboxStore {
|
|
29
|
+
private readonly context;
|
|
30
|
+
private readonly schemaName;
|
|
31
|
+
private readonly tableName;
|
|
32
|
+
private readonly leaseDurationMs;
|
|
33
|
+
private readonly claimStrategy;
|
|
34
|
+
constructor(context: KnexContext, options?: KnexOutboxStoreOptions);
|
|
35
|
+
append(events: readonly IntegrationEvent[]): Promise<void>;
|
|
36
|
+
claimBatch(limit: number, now: Date): Promise<OutboxRecord[]>;
|
|
37
|
+
markPublished(ids: readonly string[], at: Date): Promise<void>;
|
|
38
|
+
markFailed(id: string, error: {
|
|
39
|
+
readonly code: string;
|
|
40
|
+
}, nextAttemptAt: Date | null): Promise<void>;
|
|
41
|
+
retryDead(ids: readonly string[], now: Date): Promise<number>;
|
|
42
|
+
purgePublished(before: Date): Promise<number>;
|
|
43
|
+
stats(): Promise<OutboxStats>;
|
|
44
|
+
/**
|
|
45
|
+
* Selects the due rows `FOR UPDATE SKIP LOCKED`, so concurrent relays pass over each other's
|
|
46
|
+
* rows instead of waiting, then leases them in the same transaction by pushing
|
|
47
|
+
* `next_attempt_at` forward. The row locks last a few milliseconds; the lease is what covers
|
|
48
|
+
* the publication.
|
|
49
|
+
*/
|
|
50
|
+
private claimSkippingLocked;
|
|
51
|
+
/**
|
|
52
|
+
* Takes each due row with an update that only applies if nobody claimed it since it was
|
|
53
|
+
* read: every claim increments `attempts`, so an unchanged count on a row that is still due
|
|
54
|
+
* means the row is free. Rows lost to another relay are replaced by reading again.
|
|
55
|
+
*/
|
|
56
|
+
private claimByCompareAndSet;
|
|
57
|
+
private rows;
|
|
58
|
+
private due;
|
|
59
|
+
private lease;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=knex-outbox-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knex-outbox-store.d.ts","sourceRoot":"","sources":["../../src/outbox/knex-outbox-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAgB,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAG/F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAGnE;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,iBAAiB,CAAC;AAEpE,MAAM,WAAW,sBAAsB;IACrC,0FAA0F;IAC1F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,wBAAwB;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAsBD;;;;GAIG;AACH,qBAAa,eAAgB,YAAW,WAAW;IACjD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IAEtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAEhD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAEhE,YAAY,OAAO,EAAE,WAAW,EAAE,OAAO,GAAE,sBAA2B,EAMrE;IAEK,MAAM,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB/D;IAEK,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAqBlE;IAEK,aAAa,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAQnE;IAEK,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,aAAa,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CASxG;IAEK,SAAS,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CASlE;IAEK,cAAc,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlD;IAEK,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC,CAclC;IAED;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;OAIG;YACW,oBAAoB;IA2BlC,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,GAAG;IASX,OAAO,CAAC,KAAK;CAMd"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { dialectOf } from '../sql/dialect-of.js';
|
|
2
|
+
import { OUTBOX_TABLE } from './table-names.js';
|
|
3
|
+
// A fresh record is due at once, whatever clock the relay runs on.
|
|
4
|
+
const DUE_IMMEDIATELY = new Date(0);
|
|
5
|
+
/**
|
|
6
|
+
* The outbox in a SQL database. `append` runs on the executor of the context: called inside a
|
|
7
|
+
* unit of work, the events are written by the transaction of the command and share its fate,
|
|
8
|
+
* which is the whole point of an outbox.
|
|
9
|
+
*/
|
|
10
|
+
export class KnexOutboxStore {
|
|
11
|
+
context;
|
|
12
|
+
schemaName;
|
|
13
|
+
tableName;
|
|
14
|
+
leaseDurationMs;
|
|
15
|
+
claimStrategy;
|
|
16
|
+
constructor(context, options = {}) {
|
|
17
|
+
this.context = context;
|
|
18
|
+
this.schemaName = options.schema;
|
|
19
|
+
this.tableName = options.table ?? OUTBOX_TABLE;
|
|
20
|
+
this.leaseDurationMs = options.leaseMs ?? 30_000;
|
|
21
|
+
this.claimStrategy = options.claim;
|
|
22
|
+
}
|
|
23
|
+
async append(events) {
|
|
24
|
+
if (events.length === 0) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await this.rows(this.context.executor()).insert(events.map((event) => ({
|
|
28
|
+
id: event.id,
|
|
29
|
+
type: event.type,
|
|
30
|
+
version: event.version,
|
|
31
|
+
occurred_at: event.occurredAt,
|
|
32
|
+
// Serialized here: drivers would send a JS array as a SQL array, not as JSON.
|
|
33
|
+
payload: event.payload === undefined ? null : JSON.stringify(event.payload),
|
|
34
|
+
traceparent: event.traceparent ?? null,
|
|
35
|
+
correlation_id: event.correlationId ?? null,
|
|
36
|
+
next_attempt_at: DUE_IMMEDIATELY,
|
|
37
|
+
})));
|
|
38
|
+
}
|
|
39
|
+
async claimBatch(limit, now) {
|
|
40
|
+
if (limit <= 0) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const executor = this.context.executor();
|
|
44
|
+
const dialect = dialectOf(executor);
|
|
45
|
+
const strategy = this.claimStrategy ?? (dialect === 'postgres' || dialect === 'mysql' ? 'skip-locked' : 'compare-and-set');
|
|
46
|
+
const claimed = strategy === 'skip-locked'
|
|
47
|
+
? await this.claimSkippingLocked(executor, limit, now)
|
|
48
|
+
: await this.claimByCompareAndSet(executor, limit, now);
|
|
49
|
+
const leasedUntil = new Date(now.getTime() + this.leaseDurationMs);
|
|
50
|
+
return claimed.map((row) => toRecord({ ...row, attempts: row.attempts + 1, next_attempt_at: leasedUntil }, dialect));
|
|
51
|
+
}
|
|
52
|
+
async markPublished(ids, at) {
|
|
53
|
+
if (ids.length === 0) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await this.rows(this.context.executor())
|
|
57
|
+
.whereIn('id', [...ids])
|
|
58
|
+
.update({ status: 'published', published_at: at });
|
|
59
|
+
}
|
|
60
|
+
async markFailed(id, error, nextAttemptAt) {
|
|
61
|
+
await this.rows(this.context.executor())
|
|
62
|
+
.where({ id })
|
|
63
|
+
.whereNot({ status: 'published' })
|
|
64
|
+
.update(nextAttemptAt === null
|
|
65
|
+
? { status: 'dead', last_error_code: error.code }
|
|
66
|
+
: { next_attempt_at: nextAttemptAt, last_error_code: error.code });
|
|
67
|
+
}
|
|
68
|
+
async retryDead(ids, now) {
|
|
69
|
+
if (ids.length === 0) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
return this.rows(this.context.executor())
|
|
73
|
+
.whereIn('id', [...ids])
|
|
74
|
+
.where({ status: 'dead' })
|
|
75
|
+
.update({ status: 'pending', attempts: 0, next_attempt_at: now });
|
|
76
|
+
}
|
|
77
|
+
async purgePublished(before) {
|
|
78
|
+
return this.rows(this.context.executor())
|
|
79
|
+
.where({ status: 'published' })
|
|
80
|
+
.where('published_at', '<', before)
|
|
81
|
+
.delete();
|
|
82
|
+
}
|
|
83
|
+
async stats() {
|
|
84
|
+
const executor = this.context.executor();
|
|
85
|
+
// Portable form of `count(*) filter (where ...)`.
|
|
86
|
+
const count = (condition, alias) => executor.raw(`sum(case when ${condition} then 1 else 0 end) as ??`, [alias]);
|
|
87
|
+
const row = await this.rows(executor).first(count("status = 'pending'", 'pending'), count("status = 'pending' and last_error_code is not null", 'failed'), count("status = 'dead'", 'dead'));
|
|
88
|
+
return { pending: countAt(row, 'pending'), failed: countAt(row, 'failed'), dead: countAt(row, 'dead') };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Selects the due rows `FOR UPDATE SKIP LOCKED`, so concurrent relays pass over each other's
|
|
92
|
+
* rows instead of waiting, then leases them in the same transaction by pushing
|
|
93
|
+
* `next_attempt_at` forward. The row locks last a few milliseconds; the lease is what covers
|
|
94
|
+
* the publication.
|
|
95
|
+
*/
|
|
96
|
+
claimSkippingLocked(executor, limit, now) {
|
|
97
|
+
return executor.transaction(async (transaction) => {
|
|
98
|
+
const due = await this.due(transaction, limit, now).forUpdate().skipLocked();
|
|
99
|
+
if (due.length > 0) {
|
|
100
|
+
await this.rows(transaction)
|
|
101
|
+
.whereIn('id', due.map((row) => row.id))
|
|
102
|
+
.update(this.lease(transaction, now));
|
|
103
|
+
}
|
|
104
|
+
return due;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Takes each due row with an update that only applies if nobody claimed it since it was
|
|
109
|
+
* read: every claim increments `attempts`, so an unchanged count on a row that is still due
|
|
110
|
+
* means the row is free. Rows lost to another relay are replaced by reading again.
|
|
111
|
+
*/
|
|
112
|
+
async claimByCompareAndSet(executor, limit, now) {
|
|
113
|
+
const claimed = [];
|
|
114
|
+
while (claimed.length < limit) {
|
|
115
|
+
const wanted = limit - claimed.length;
|
|
116
|
+
const due = await this.due(executor, wanted, now);
|
|
117
|
+
for (const row of due) {
|
|
118
|
+
const taken = await this.rows(executor)
|
|
119
|
+
.where({ id: row.id, status: 'pending', attempts: row.attempts })
|
|
120
|
+
.where('next_attempt_at', '<=', now)
|
|
121
|
+
.update(this.lease(executor, now));
|
|
122
|
+
if (taken === 1) {
|
|
123
|
+
claimed.push(row);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (due.length < wanted) {
|
|
127
|
+
// Nothing else is due: the rows lost to another relay need no replacement.
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return claimed;
|
|
132
|
+
}
|
|
133
|
+
rows(executor) {
|
|
134
|
+
const table = executor(this.tableName);
|
|
135
|
+
return this.schemaName === undefined ? table : table.withSchema(this.schemaName);
|
|
136
|
+
}
|
|
137
|
+
due(executor, limit, now) {
|
|
138
|
+
return this.rows(executor)
|
|
139
|
+
.select('*')
|
|
140
|
+
.where({ status: 'pending' })
|
|
141
|
+
.where('next_attempt_at', '<=', now)
|
|
142
|
+
.orderBy([{ column: 'next_attempt_at' }, { column: 'sequence' }])
|
|
143
|
+
.limit(limit);
|
|
144
|
+
}
|
|
145
|
+
lease(executor, now) {
|
|
146
|
+
return {
|
|
147
|
+
attempts: executor.raw('?? + 1', ['attempts']),
|
|
148
|
+
next_attempt_at: new Date(now.getTime() + this.leaseDurationMs),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** A sum is a bigint or a decimal, which drivers return as a string, and NULL over no row. */
|
|
153
|
+
function countAt(row, column) {
|
|
154
|
+
if (typeof row !== 'object' || row === null) {
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
const value = Object.entries(row).find(([key]) => key === column)?.[1];
|
|
158
|
+
return Number(value ?? 0);
|
|
159
|
+
}
|
|
160
|
+
function toRecord(row, dialect) {
|
|
161
|
+
// SQLite has no JSON type: the driver hands the text back. Elsewhere the driver has parsed
|
|
162
|
+
// the column already, and a payload that is itself a string must not be parsed twice.
|
|
163
|
+
const payload = dialect === 'sqlite' && typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload;
|
|
164
|
+
return {
|
|
165
|
+
event: {
|
|
166
|
+
id: row.id,
|
|
167
|
+
type: row.type,
|
|
168
|
+
version: row.version,
|
|
169
|
+
occurredAt: row.occurred_at,
|
|
170
|
+
payload,
|
|
171
|
+
...(row.traceparent === null ? {} : { traceparent: row.traceparent }),
|
|
172
|
+
...(row.correlation_id === null ? {} : { correlationId: row.correlation_id }),
|
|
173
|
+
},
|
|
174
|
+
status: row.status,
|
|
175
|
+
attempts: row.attempts,
|
|
176
|
+
nextAttemptAt: toDate(row.next_attempt_at),
|
|
177
|
+
...(row.last_error_code === null ? {} : { lastErrorCode: row.last_error_code }),
|
|
178
|
+
...(row.published_at === null ? {} : { publishedAt: toDate(row.published_at) }),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function toDate(value) {
|
|
182
|
+
return value instanceof Date ? value : new Date(value);
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=knex-outbox-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knex-outbox-store.js","sourceRoot":"","sources":["../../src/outbox/knex-outbox-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProcessedEventStore } from '@ddd-ts-kit/kernel';
|
|
2
|
+
import type { KnexContext } from '../unit-of-work/knex-context.js';
|
|
3
|
+
export interface KnexProcessedEventStoreOptions {
|
|
4
|
+
/** The schema the table was created in. Default: the default schema of the connection. */
|
|
5
|
+
readonly schema?: string;
|
|
6
|
+
/** Default `processed_events`. */
|
|
7
|
+
readonly table?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Idempotency of consumers. It writes on the executor of the context: when the consumer runs
|
|
11
|
+
* inside a unit of work, the mark commits with the effects of the handler and disappears with
|
|
12
|
+
* them if the handler fails, so the redelivery is processed.
|
|
13
|
+
*/
|
|
14
|
+
export declare class KnexProcessedEventStore implements ProcessedEventStore {
|
|
15
|
+
private readonly context;
|
|
16
|
+
private readonly schemaName;
|
|
17
|
+
private readonly tableName;
|
|
18
|
+
constructor(context: KnexContext, options?: KnexProcessedEventStoreOptions);
|
|
19
|
+
markProcessed(consumer: string, eventId: string): Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=knex-processed-event-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knex-processed-event-store.d.ts","sourceRoot":"","sources":["../../src/outbox/knex-processed-event-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAGnE,MAAM,WAAW,8BAA8B;IAC7C,0FAA0F;IAC1F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,YAAW,mBAAmB;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IAEtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAEhD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,YAAY,OAAO,EAAE,WAAW,EAAE,OAAO,GAAE,8BAAmC,EAI7E;IAED,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQjE;CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { insertIgnoringConflict } from '../sql/insert-ignoring-conflict.js';
|
|
2
|
+
import { PROCESSED_EVENTS_TABLE } from './table-names.js';
|
|
3
|
+
/**
|
|
4
|
+
* Idempotency of consumers. It writes on the executor of the context: when the consumer runs
|
|
5
|
+
* inside a unit of work, the mark commits with the effects of the handler and disappears with
|
|
6
|
+
* them if the handler fails, so the redelivery is processed.
|
|
7
|
+
*/
|
|
8
|
+
export class KnexProcessedEventStore {
|
|
9
|
+
context;
|
|
10
|
+
schemaName;
|
|
11
|
+
tableName;
|
|
12
|
+
constructor(context, options = {}) {
|
|
13
|
+
this.context = context;
|
|
14
|
+
this.schemaName = options.schema;
|
|
15
|
+
this.tableName = options.table ?? PROCESSED_EVENTS_TABLE;
|
|
16
|
+
}
|
|
17
|
+
markProcessed(consumer, eventId) {
|
|
18
|
+
const executor = this.context.executor();
|
|
19
|
+
const table = executor(this.tableName);
|
|
20
|
+
return insertIgnoringConflict(executor, this.schemaName === undefined ? table : table.withSchema(this.schemaName), {
|
|
21
|
+
row: { consumer, event_id: eventId },
|
|
22
|
+
conflict: ['consumer', 'event_id'],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=knex-processed-event-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knex-processed-event-store.js","sourceRoot":"","sources":["../../src/outbox/knex-processed-event-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAE5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAS1D;;;;GAIG;AACH,MAAM,OAAO,uBAAuB;IACjB,OAAO,CAAc;IAErB,UAAU,CAAqB;IAE/B,SAAS,CAAS;IAEnC,YAAY,OAAoB,EAAE,OAAO,GAAmC,EAAE;QAC5E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,sBAAsB,CAAC;IAC3D,CAAC;IAED,aAAa,CAAC,QAAgB,EAAE,OAAe;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEvC,OAAO,sBAAsB,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;YACjH,GAAG,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE;YACpC,QAAQ,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC;SACnC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface OutboxTablesOptions {
|
|
2
|
+
/**
|
|
3
|
+
* The schema (PostgreSQL) or database (MySQL) holding the tables; created when missing.
|
|
4
|
+
* Without it the tables live in the default schema of the connection.
|
|
5
|
+
*/
|
|
6
|
+
readonly schema?: string;
|
|
7
|
+
/** Default `outbox`. */
|
|
8
|
+
readonly outboxTable?: string;
|
|
9
|
+
/** Default `processed_events`. */
|
|
10
|
+
readonly processedEventsTable?: string;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=outbox-tables-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox-tables-options.d.ts","sourceRoot":"","sources":["../../src/outbox/outbox-tables-options.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,wBAAwB;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,kCAAkC;IAClC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox-tables-options.js","sourceRoot":"","sources":["../../src/outbox/outbox-tables-options.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-names.d.ts","sourceRoot":"","sources":["../../src/outbox/table-names.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,WAAW,CAAC;AAErC,eAAO,MAAM,sBAAsB,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-names.js","sourceRoot":"","sources":["../../src/outbox/table-names.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAErC,MAAM,CAAC,MAAM,sBAAsB,GAAG,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column-of.d.ts","sourceRoot":"","sources":["../../src/read-model/column-of.ts"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQzF"}
|