node-cqrs 1.2.0-beta.0 → 1.3.0-alpha.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/CHANGELOG.md +13 -0
- package/README.md +29 -9
- package/dist/cjs/postgresql/AbstractPostgresqlAccessor.js +83 -0
- package/dist/cjs/postgresql/AbstractPostgresqlAccessor.js.map +1 -0
- package/dist/cjs/postgresql/AbstractPostgresqlObjectProjection.js +36 -0
- package/dist/cjs/postgresql/AbstractPostgresqlObjectProjection.js.map +1 -0
- package/dist/cjs/postgresql/AbstractPostgresqlView.js +53 -0
- package/dist/cjs/postgresql/AbstractPostgresqlView.js.map +1 -0
- package/dist/cjs/postgresql/IContainer.js +3 -0
- package/dist/cjs/postgresql/IContainer.js.map +1 -0
- package/dist/cjs/postgresql/PostgresqlConnection.js +3 -0
- package/dist/cjs/postgresql/PostgresqlConnection.js.map +1 -0
- package/dist/cjs/postgresql/PostgresqlEventLocker.js +127 -0
- package/dist/cjs/postgresql/PostgresqlEventLocker.js.map +1 -0
- package/dist/cjs/postgresql/PostgresqlEventStorage.js +315 -0
- package/dist/cjs/postgresql/PostgresqlEventStorage.js.map +1 -0
- package/dist/cjs/postgresql/PostgresqlObjectStorage.js +137 -0
- package/dist/cjs/postgresql/PostgresqlObjectStorage.js.map +1 -0
- package/dist/cjs/postgresql/PostgresqlObjectView.js +46 -0
- package/dist/cjs/postgresql/PostgresqlObjectView.js.map +1 -0
- package/dist/cjs/postgresql/PostgresqlProjectionDataParams.js +3 -0
- package/dist/cjs/postgresql/PostgresqlProjectionDataParams.js.map +1 -0
- package/dist/cjs/postgresql/PostgresqlViewLocker.js +151 -0
- package/dist/cjs/postgresql/PostgresqlViewLocker.js.map +1 -0
- package/dist/cjs/postgresql/index.js +29 -0
- package/dist/cjs/postgresql/index.js.map +1 -0
- package/dist/cjs/postgresql/utils/getEventId.js +14 -0
- package/dist/cjs/postgresql/utils/getEventId.js.map +1 -0
- package/dist/cjs/postgresql/utils/index.js +19 -0
- package/dist/cjs/postgresql/utils/index.js.map +1 -0
- package/dist/cjs/postgresql/utils/quoteIdentifier.js +14 -0
- package/dist/cjs/postgresql/utils/quoteIdentifier.js.map +1 -0
- package/dist/cjs/sqlite/SqliteEventStorage.js +66 -36
- package/dist/cjs/sqlite/SqliteEventStorage.js.map +1 -1
- package/dist/esm/postgresql/AbstractPostgresqlAccessor.js +79 -0
- package/dist/esm/postgresql/AbstractPostgresqlAccessor.js.map +1 -0
- package/dist/esm/postgresql/AbstractPostgresqlObjectProjection.js +32 -0
- package/dist/esm/postgresql/AbstractPostgresqlObjectProjection.js.map +1 -0
- package/dist/esm/postgresql/AbstractPostgresqlView.js +49 -0
- package/dist/esm/postgresql/AbstractPostgresqlView.js.map +1 -0
- package/dist/esm/postgresql/IContainer.js +2 -0
- package/dist/esm/postgresql/IContainer.js.map +1 -0
- package/dist/esm/postgresql/PostgresqlConnection.js +2 -0
- package/dist/esm/postgresql/PostgresqlConnection.js.map +1 -0
- package/dist/esm/postgresql/PostgresqlEventLocker.js +123 -0
- package/dist/esm/postgresql/PostgresqlEventLocker.js.map +1 -0
- package/dist/esm/postgresql/PostgresqlEventStorage.js +311 -0
- package/dist/esm/postgresql/PostgresqlEventStorage.js.map +1 -0
- package/dist/esm/postgresql/PostgresqlObjectStorage.js +133 -0
- package/dist/esm/postgresql/PostgresqlObjectStorage.js.map +1 -0
- package/dist/esm/postgresql/PostgresqlObjectView.js +42 -0
- package/dist/esm/postgresql/PostgresqlObjectView.js.map +1 -0
- package/dist/esm/postgresql/PostgresqlProjectionDataParams.js +2 -0
- package/dist/esm/postgresql/PostgresqlProjectionDataParams.js.map +1 -0
- package/dist/esm/postgresql/PostgresqlViewLocker.js +147 -0
- package/dist/esm/postgresql/PostgresqlViewLocker.js.map +1 -0
- package/dist/esm/postgresql/index.js +13 -0
- package/dist/esm/postgresql/index.js.map +1 -0
- package/dist/esm/postgresql/utils/getEventId.js +10 -0
- package/dist/esm/postgresql/utils/getEventId.js.map +1 -0
- package/dist/esm/postgresql/utils/index.js +3 -0
- package/dist/esm/postgresql/utils/index.js.map +1 -0
- package/dist/esm/postgresql/utils/quoteIdentifier.js +11 -0
- package/dist/esm/postgresql/utils/quoteIdentifier.js.map +1 -0
- package/dist/esm/sqlite/SqliteEventStorage.js +66 -36
- package/dist/esm/sqlite/SqliteEventStorage.js.map +1 -1
- package/dist/types/postgresql/AbstractPostgresqlAccessor.d.ts +29 -0
- package/dist/types/postgresql/AbstractPostgresqlObjectProjection.d.ts +12 -0
- package/dist/types/postgresql/AbstractPostgresqlView.d.ts +23 -0
- package/dist/types/postgresql/IContainer.d.ts +12 -0
- package/dist/types/postgresql/PostgresqlConnection.d.ts +7 -0
- package/dist/types/postgresql/PostgresqlEventLocker.d.ts +39 -0
- package/dist/types/postgresql/PostgresqlEventStorage.d.ts +23 -0
- package/dist/types/postgresql/PostgresqlObjectStorage.d.ts +29 -0
- package/dist/types/postgresql/PostgresqlObjectView.d.ts +19 -0
- package/dist/types/postgresql/PostgresqlProjectionDataParams.d.ts +14 -0
- package/dist/types/postgresql/PostgresqlViewLocker.d.ts +41 -0
- package/dist/types/postgresql/index.d.ts +12 -0
- package/dist/types/postgresql/utils/getEventId.d.ts +5 -0
- package/dist/types/postgresql/utils/index.d.ts +2 -0
- package/dist/types/postgresql/utils/quoteIdentifier.d.ts +1 -0
- package/package.json +20 -5
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { assertNonNegativeInteger, assertString } from "../utils/assert.js";
|
|
2
|
+
import { AbstractPostgresqlAccessor } from "./AbstractPostgresqlAccessor.js";
|
|
3
|
+
import { getEventId, quoteIdentifier } from "./utils/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* PostgreSQL-backed implementation of IEventLocker.
|
|
6
|
+
*
|
|
7
|
+
* Uses one table for per-event processing locks and the view lock table for the
|
|
8
|
+
* last processed event checkpoint.
|
|
9
|
+
*/
|
|
10
|
+
export class PostgresqlEventLocker extends AbstractPostgresqlAccessor {
|
|
11
|
+
static DEFAULT_EVENT_LOCK_TTL = 15_000;
|
|
12
|
+
static DEFAULT_EVENT_LOCK_TABLE = 'ncqrs_event_locks';
|
|
13
|
+
static DEFAULT_VIEW_LOCK_TABLE = 'ncqrs_view_locks';
|
|
14
|
+
#projectionName;
|
|
15
|
+
#schemaVersion;
|
|
16
|
+
#viewLockTableName;
|
|
17
|
+
#eventLockTableName;
|
|
18
|
+
#eventLockTtl;
|
|
19
|
+
constructor(o) {
|
|
20
|
+
super(o);
|
|
21
|
+
assertString(o.projectionName, 'o.projectionName');
|
|
22
|
+
assertString(o.schemaVersion, 'o.schemaVersion');
|
|
23
|
+
if (o.viewLockTableName !== undefined)
|
|
24
|
+
assertString(o.viewLockTableName, 'o.viewLockTableName');
|
|
25
|
+
if (o.eventLockTableName !== undefined)
|
|
26
|
+
assertString(o.eventLockTableName, 'o.eventLockTableName');
|
|
27
|
+
if (o.eventLockTtl !== undefined)
|
|
28
|
+
assertNonNegativeInteger(o.eventLockTtl, 'o.eventLockTtl');
|
|
29
|
+
this.#projectionName = o.projectionName;
|
|
30
|
+
this.#schemaVersion = o.schemaVersion;
|
|
31
|
+
this.#viewLockTableName = quoteIdentifier(o.viewLockTableName ?? PostgresqlEventLocker.DEFAULT_VIEW_LOCK_TABLE);
|
|
32
|
+
this.#eventLockTableName = quoteIdentifier(o.eventLockTableName ?? PostgresqlEventLocker.DEFAULT_EVENT_LOCK_TABLE);
|
|
33
|
+
this.#eventLockTtl = o.eventLockTtl ?? PostgresqlEventLocker.DEFAULT_EVENT_LOCK_TTL;
|
|
34
|
+
}
|
|
35
|
+
async initialize(db) {
|
|
36
|
+
await db.query(`
|
|
37
|
+
CREATE TABLE IF NOT EXISTS ${this.#viewLockTableName} (
|
|
38
|
+
projection_name text NOT NULL,
|
|
39
|
+
schema_version text NOT NULL,
|
|
40
|
+
locked_till timestamptz NULL,
|
|
41
|
+
lock_token text NULL,
|
|
42
|
+
last_event text NULL,
|
|
43
|
+
PRIMARY KEY (projection_name, schema_version)
|
|
44
|
+
)
|
|
45
|
+
`);
|
|
46
|
+
await db.query(`
|
|
47
|
+
CREATE TABLE IF NOT EXISTS ${this.#eventLockTableName} (
|
|
48
|
+
projection_name text NOT NULL,
|
|
49
|
+
schema_version text NOT NULL,
|
|
50
|
+
event_id text NOT NULL,
|
|
51
|
+
processing_at timestamptz NOT NULL DEFAULT NOW(),
|
|
52
|
+
processed_at timestamptz NULL,
|
|
53
|
+
PRIMARY KEY (projection_name, schema_version, event_id)
|
|
54
|
+
)
|
|
55
|
+
`);
|
|
56
|
+
await db.query(`
|
|
57
|
+
CREATE INDEX IF NOT EXISTS ${quoteIdentifier(`${this.#tableNameForIndex()}_processing_at_idx`)}
|
|
58
|
+
ON ${this.#eventLockTableName} (processing_at)
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
async tryMarkAsProjecting(event) {
|
|
62
|
+
await this.assertConnection();
|
|
63
|
+
const eventId = getEventId(event);
|
|
64
|
+
const r = await this.connection.query(`
|
|
65
|
+
INSERT INTO ${this.#eventLockTableName} AS event_lock
|
|
66
|
+
(projection_name, schema_version, event_id, processing_at, processed_at)
|
|
67
|
+
VALUES ($1, $2, $3, NOW(), NULL)
|
|
68
|
+
ON CONFLICT (projection_name, schema_version, event_id)
|
|
69
|
+
DO UPDATE SET
|
|
70
|
+
processing_at = NOW(),
|
|
71
|
+
processed_at = NULL
|
|
72
|
+
WHERE
|
|
73
|
+
event_lock.processed_at IS NULL
|
|
74
|
+
AND event_lock.processing_at <= NOW() - ($4::double precision * INTERVAL '1 millisecond')
|
|
75
|
+
`, [this.#projectionName, this.#schemaVersion, eventId, this.#eventLockTtl]);
|
|
76
|
+
return r.rowCount !== 0;
|
|
77
|
+
}
|
|
78
|
+
async markAsProjected(event) {
|
|
79
|
+
await this.assertConnection();
|
|
80
|
+
const eventId = getEventId(event);
|
|
81
|
+
const result = await this.connection.query(`
|
|
82
|
+
UPDATE ${this.#eventLockTableName}
|
|
83
|
+
SET
|
|
84
|
+
processed_at = NOW()
|
|
85
|
+
WHERE
|
|
86
|
+
projection_name = $1
|
|
87
|
+
AND schema_version = $2
|
|
88
|
+
AND event_id = $3
|
|
89
|
+
AND processed_at IS NULL
|
|
90
|
+
`, [this.#projectionName, this.#schemaVersion, eventId]);
|
|
91
|
+
if (result.rowCount !== 1)
|
|
92
|
+
throw new Error(`Event ${event.id} could not be marked as processed`);
|
|
93
|
+
}
|
|
94
|
+
async markAsLastEvent(event) {
|
|
95
|
+
await this.assertConnection();
|
|
96
|
+
await this.connection.query(`
|
|
97
|
+
INSERT INTO ${this.#viewLockTableName} (projection_name, schema_version, last_event)
|
|
98
|
+
VALUES ($1, $2, $3)
|
|
99
|
+
ON CONFLICT (projection_name, schema_version)
|
|
100
|
+
DO UPDATE SET
|
|
101
|
+
last_event = excluded.last_event
|
|
102
|
+
`, [this.#projectionName, this.#schemaVersion, JSON.stringify(event)]);
|
|
103
|
+
}
|
|
104
|
+
async getLastEvent() {
|
|
105
|
+
await this.assertConnection();
|
|
106
|
+
const viewInfoRecord = await this.connection.query(`
|
|
107
|
+
SELECT
|
|
108
|
+
last_event
|
|
109
|
+
FROM ${this.#viewLockTableName}
|
|
110
|
+
WHERE
|
|
111
|
+
projection_name = $1
|
|
112
|
+
AND schema_version = $2
|
|
113
|
+
`, [this.#projectionName, this.#schemaVersion]);
|
|
114
|
+
const lastEvent = viewInfoRecord.rows[0]?.last_event;
|
|
115
|
+
if (!lastEvent)
|
|
116
|
+
return undefined;
|
|
117
|
+
return JSON.parse(lastEvent);
|
|
118
|
+
}
|
|
119
|
+
#tableNameForIndex() {
|
|
120
|
+
return this.#eventLockTableName.replaceAll('"', '').replaceAll('.', '_');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=PostgresqlEventLocker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresqlEventLocker.js","sourceRoot":"","sources":["../../../src/postgresql/PostgresqlEventLocker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { ConcurrencyError } from "../errors/index.js";
|
|
3
|
+
import { assertString, parseSagaId } from "../utils/index.js";
|
|
4
|
+
import { AbstractPostgresqlAccessor } from "./AbstractPostgresqlAccessor.js";
|
|
5
|
+
import { quoteIdentifier } from "./utils/index.js";
|
|
6
|
+
function extractMeta(envelope) {
|
|
7
|
+
const { event: _event, ignoreConcurrencyError: _ignore, ...rest } = envelope;
|
|
8
|
+
if (Object.keys(rest).length === 0)
|
|
9
|
+
return null;
|
|
10
|
+
return rest;
|
|
11
|
+
}
|
|
12
|
+
function parseJson(value) {
|
|
13
|
+
if (typeof value !== 'string')
|
|
14
|
+
return value;
|
|
15
|
+
return JSON.parse(value);
|
|
16
|
+
}
|
|
17
|
+
function indexPrefix(tableName) {
|
|
18
|
+
return tableName.replaceAll('"', '').replaceAll('.', '_');
|
|
19
|
+
}
|
|
20
|
+
function reconstructEvent(row) {
|
|
21
|
+
const data = parseJson(row.data);
|
|
22
|
+
const event = {
|
|
23
|
+
id: row.id,
|
|
24
|
+
...data
|
|
25
|
+
};
|
|
26
|
+
if (row.saga_origins)
|
|
27
|
+
event.sagaOrigins = parseJson(row.saga_origins);
|
|
28
|
+
return event;
|
|
29
|
+
}
|
|
30
|
+
export class PostgresqlEventStorage extends AbstractPostgresqlAccessor {
|
|
31
|
+
static EVENTS_TABLE = 'tbl_events';
|
|
32
|
+
static EVENT_SAGAS_TABLE = 'tbl_event_sagas';
|
|
33
|
+
#eventsTableName;
|
|
34
|
+
#eventSagasTableName;
|
|
35
|
+
#aggregateVersionIndexName;
|
|
36
|
+
constructor({ postgresqlEventStorageConfig, viewModelPostgresqlDb, viewModelPostgresqlDbFactory }) {
|
|
37
|
+
super({ viewModelPostgresqlDb, viewModelPostgresqlDbFactory });
|
|
38
|
+
const eventsTableName = postgresqlEventStorageConfig?.eventsTableName ?? PostgresqlEventStorage.EVENTS_TABLE;
|
|
39
|
+
const eventSagasTableName = postgresqlEventStorageConfig?.eventSagasTableName ??
|
|
40
|
+
PostgresqlEventStorage.EVENT_SAGAS_TABLE;
|
|
41
|
+
assertString(eventsTableName, 'postgresqlEventStorageConfig.eventsTableName');
|
|
42
|
+
assertString(eventSagasTableName, 'postgresqlEventStorageConfig.eventSagasTableName');
|
|
43
|
+
this.#eventsTableName = quoteIdentifier(eventsTableName);
|
|
44
|
+
this.#eventSagasTableName = quoteIdentifier(eventSagasTableName);
|
|
45
|
+
this.#aggregateVersionIndexName = `${indexPrefix(this.#eventsTableName)}_aggregate_version_unique_idx`;
|
|
46
|
+
}
|
|
47
|
+
async initialize(db) {
|
|
48
|
+
await db.query(`
|
|
49
|
+
CREATE TABLE IF NOT EXISTS ${this.#eventsTableName} (
|
|
50
|
+
position BIGSERIAL PRIMARY KEY,
|
|
51
|
+
id TEXT NOT NULL UNIQUE,
|
|
52
|
+
aggregate_id TEXT,
|
|
53
|
+
aggregate_version INTEGER,
|
|
54
|
+
type TEXT NOT NULL,
|
|
55
|
+
data JSONB NOT NULL,
|
|
56
|
+
meta JSONB,
|
|
57
|
+
check_concurrency BOOLEAN NOT NULL DEFAULT TRUE
|
|
58
|
+
)
|
|
59
|
+
`);
|
|
60
|
+
await db.query(`
|
|
61
|
+
CREATE TABLE IF NOT EXISTS ${this.#eventSagasTableName} (
|
|
62
|
+
saga_descriptor TEXT NOT NULL,
|
|
63
|
+
origin_id TEXT NOT NULL,
|
|
64
|
+
event_id TEXT NOT NULL,
|
|
65
|
+
PRIMARY KEY (saga_descriptor, origin_id, event_id),
|
|
66
|
+
FOREIGN KEY (event_id) REFERENCES ${this.#eventsTableName}(id) ON DELETE CASCADE
|
|
67
|
+
)
|
|
68
|
+
`);
|
|
69
|
+
const eventsIndexPrefix = indexPrefix(this.#eventsTableName);
|
|
70
|
+
const sagasIndexPrefix = indexPrefix(this.#eventSagasTableName);
|
|
71
|
+
await db.query(`
|
|
72
|
+
CREATE INDEX IF NOT EXISTS "${eventsIndexPrefix}_aggregate_id_idx"
|
|
73
|
+
ON ${this.#eventsTableName} (aggregate_id, aggregate_version)
|
|
74
|
+
`);
|
|
75
|
+
// Only checked rows participate in aggregate-version uniqueness, so ignoreConcurrencyError can opt out.
|
|
76
|
+
await db.query(`
|
|
77
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "${this.#aggregateVersionIndexName}"
|
|
78
|
+
ON ${this.#eventsTableName} (aggregate_id, aggregate_version)
|
|
79
|
+
WHERE check_concurrency
|
|
80
|
+
AND aggregate_id IS NOT NULL
|
|
81
|
+
AND aggregate_version IS NOT NULL
|
|
82
|
+
`);
|
|
83
|
+
await db.query(`
|
|
84
|
+
CREATE INDEX IF NOT EXISTS "${eventsIndexPrefix}_type_position_idx"
|
|
85
|
+
ON ${this.#eventsTableName} (type, position)
|
|
86
|
+
`);
|
|
87
|
+
await db.query(`
|
|
88
|
+
CREATE INDEX IF NOT EXISTS "${sagasIndexPrefix}_event_id_idx"
|
|
89
|
+
ON ${this.#eventSagasTableName} (event_id)
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
// eslint-disable-next-line class-methods-use-this
|
|
93
|
+
getNewId() {
|
|
94
|
+
return randomUUID().replaceAll('-', '');
|
|
95
|
+
}
|
|
96
|
+
async commitEvents(events, options) {
|
|
97
|
+
try {
|
|
98
|
+
await this.runInTransaction(async () => {
|
|
99
|
+
const metaJson = options?.meta ? JSON.stringify(options.meta) : null;
|
|
100
|
+
for (const event of events) {
|
|
101
|
+
const { sagaOrigins, id, ...eventData } = event;
|
|
102
|
+
const eventId = id ?? this.getNewId();
|
|
103
|
+
assertString(eventId, 'event.id');
|
|
104
|
+
event.id = eventId;
|
|
105
|
+
await this.connection.query(`
|
|
106
|
+
INSERT INTO ${this.#eventsTableName} (
|
|
107
|
+
id,
|
|
108
|
+
aggregate_id,
|
|
109
|
+
aggregate_version,
|
|
110
|
+
type,
|
|
111
|
+
data,
|
|
112
|
+
meta,
|
|
113
|
+
check_concurrency
|
|
114
|
+
)
|
|
115
|
+
VALUES ($1, $2, $3, $4, $5::jsonb, $6::jsonb, $7)
|
|
116
|
+
`, [
|
|
117
|
+
eventId,
|
|
118
|
+
event.aggregateId !== undefined ? String(event.aggregateId) : null,
|
|
119
|
+
event.aggregateVersion ?? null,
|
|
120
|
+
event.type,
|
|
121
|
+
JSON.stringify(eventData),
|
|
122
|
+
metaJson,
|
|
123
|
+
!options?.ignoreConcurrencyError
|
|
124
|
+
]);
|
|
125
|
+
if (sagaOrigins) {
|
|
126
|
+
for (const [descriptor, originId] of Object.entries(sagaOrigins)) {
|
|
127
|
+
await this.connection.query(`
|
|
128
|
+
INSERT INTO ${this.#eventSagasTableName} (
|
|
129
|
+
saga_descriptor,
|
|
130
|
+
origin_id,
|
|
131
|
+
event_id
|
|
132
|
+
)
|
|
133
|
+
VALUES ($1, $2, $3)
|
|
134
|
+
`, [descriptor, String(originId), eventId]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (this.isAggregateVersionUniqueViolation(error))
|
|
142
|
+
throw new ConcurrencyError('Concurrency conflict: duplicate event version', { cause: error });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
return events;
|
|
146
|
+
}
|
|
147
|
+
async *getAggregateEvents(aggregateId, options) {
|
|
148
|
+
await this.assertConnection();
|
|
149
|
+
const rows = await this.connection.query(`
|
|
150
|
+
WITH tail AS (
|
|
151
|
+
SELECT id AS tail_id
|
|
152
|
+
FROM ${this.#eventsTableName}
|
|
153
|
+
WHERE aggregate_id = $1
|
|
154
|
+
AND ($2::integer IS NULL OR aggregate_version > $2)
|
|
155
|
+
ORDER BY position DESC
|
|
156
|
+
LIMIT 1
|
|
157
|
+
)
|
|
158
|
+
SELECT
|
|
159
|
+
e.id,
|
|
160
|
+
e.aggregate_id,
|
|
161
|
+
e.aggregate_version,
|
|
162
|
+
e.type,
|
|
163
|
+
e.data,
|
|
164
|
+
e.meta,
|
|
165
|
+
e.position,
|
|
166
|
+
jsonb_object_agg(sr.saga_descriptor, sr.origin_id)
|
|
167
|
+
FILTER (WHERE sr.saga_descriptor IS NOT NULL) AS saga_origins
|
|
168
|
+
FROM ${this.#eventsTableName} e
|
|
169
|
+
LEFT JOIN tail ON TRUE
|
|
170
|
+
LEFT JOIN ${this.#eventSagasTableName} sr
|
|
171
|
+
ON sr.event_id = e.id
|
|
172
|
+
WHERE e.aggregate_id = $1
|
|
173
|
+
AND ($2::integer IS NULL OR e.aggregate_version > $2)
|
|
174
|
+
AND (
|
|
175
|
+
$3::text[] IS NULL
|
|
176
|
+
OR e.type = ANY($3::text[])
|
|
177
|
+
OR ($4 = 'last' AND e.id = tail.tail_id)
|
|
178
|
+
)
|
|
179
|
+
GROUP BY e.id, e.aggregate_id, e.aggregate_version, e.type, e.data, e.meta, e.position
|
|
180
|
+
ORDER BY e.position
|
|
181
|
+
`, [
|
|
182
|
+
String(aggregateId),
|
|
183
|
+
options?.snapshot?.aggregateVersion ?? null,
|
|
184
|
+
options?.eventTypes ?? null,
|
|
185
|
+
options?.tail ?? null
|
|
186
|
+
]);
|
|
187
|
+
for (const row of rows.rows)
|
|
188
|
+
yield reconstructEvent(row);
|
|
189
|
+
}
|
|
190
|
+
async *getSagaEvents(sagaId, { beforeEvent }) {
|
|
191
|
+
await this.assertConnection();
|
|
192
|
+
assertString(beforeEvent?.id, 'beforeEvent.id');
|
|
193
|
+
const { sagaDescriptor, originEventId } = parseSagaId(sagaId);
|
|
194
|
+
if (beforeEvent.sagaOrigins?.[sagaDescriptor] !== originEventId)
|
|
195
|
+
throw new TypeError('beforeEvent.sagaOrigins does not match sagaId');
|
|
196
|
+
const rows = await this.connection.query(`
|
|
197
|
+
WITH bounds AS (
|
|
198
|
+
SELECT
|
|
199
|
+
(SELECT position FROM ${this.#eventsTableName} WHERE id = $2) AS origin_position,
|
|
200
|
+
(SELECT position FROM ${this.#eventsTableName} WHERE id = $3) AS before_position
|
|
201
|
+
)
|
|
202
|
+
SELECT
|
|
203
|
+
b.origin_position,
|
|
204
|
+
b.before_position,
|
|
205
|
+
e.id,
|
|
206
|
+
e.aggregate_id,
|
|
207
|
+
e.aggregate_version,
|
|
208
|
+
e.type,
|
|
209
|
+
e.data,
|
|
210
|
+
e.meta,
|
|
211
|
+
e.position,
|
|
212
|
+
e.saga_origins
|
|
213
|
+
FROM bounds b
|
|
214
|
+
LEFT JOIN LATERAL (
|
|
215
|
+
SELECT
|
|
216
|
+
e.id,
|
|
217
|
+
e.aggregate_id,
|
|
218
|
+
e.aggregate_version,
|
|
219
|
+
e.type,
|
|
220
|
+
e.data,
|
|
221
|
+
e.meta,
|
|
222
|
+
e.position,
|
|
223
|
+
jsonb_object_agg(sr.saga_descriptor, sr.origin_id)
|
|
224
|
+
FILTER (WHERE sr.saga_descriptor IS NOT NULL) AS saga_origins
|
|
225
|
+
FROM ${this.#eventsTableName} e
|
|
226
|
+
LEFT JOIN ${this.#eventSagasTableName} filter_sr
|
|
227
|
+
ON filter_sr.event_id = e.id
|
|
228
|
+
AND filter_sr.saga_descriptor = $1
|
|
229
|
+
AND filter_sr.origin_id = $2
|
|
230
|
+
LEFT JOIN ${this.#eventSagasTableName} sr
|
|
231
|
+
ON sr.event_id = e.id
|
|
232
|
+
WHERE b.origin_position IS NOT NULL
|
|
233
|
+
AND b.before_position IS NOT NULL
|
|
234
|
+
AND e.position >= b.origin_position
|
|
235
|
+
AND e.position < b.before_position
|
|
236
|
+
AND (e.id = $2 OR filter_sr.event_id IS NOT NULL)
|
|
237
|
+
GROUP BY e.id, e.aggregate_id, e.aggregate_version, e.type, e.data, e.meta, e.position
|
|
238
|
+
ORDER BY e.position
|
|
239
|
+
) e ON TRUE
|
|
240
|
+
ORDER BY e.position
|
|
241
|
+
`, [sagaDescriptor, originEventId, beforeEvent.id]);
|
|
242
|
+
const bounds = rows.rows[0];
|
|
243
|
+
if (!bounds?.origin_position)
|
|
244
|
+
throw new Error(`origin event ${originEventId} not found`);
|
|
245
|
+
if (!bounds.before_position)
|
|
246
|
+
throw new Error(`beforeEvent ${beforeEvent.id} not found`);
|
|
247
|
+
for (const row of rows.rows) {
|
|
248
|
+
if (row.id)
|
|
249
|
+
yield reconstructEvent(row);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async *getEventsByTypes(eventTypes, options) {
|
|
253
|
+
await this.assertConnection();
|
|
254
|
+
const lastEventId = options?.afterEvent?.id;
|
|
255
|
+
if (options?.afterEvent)
|
|
256
|
+
assertString(options.afterEvent.id, 'options.afterEvent.id');
|
|
257
|
+
let afterPosition = 0;
|
|
258
|
+
if (lastEventId) {
|
|
259
|
+
const row = await this.connection.query(`
|
|
260
|
+
SELECT position
|
|
261
|
+
FROM ${this.#eventsTableName}
|
|
262
|
+
WHERE id = $1
|
|
263
|
+
`, [lastEventId]);
|
|
264
|
+
if (!row.rows.length)
|
|
265
|
+
return;
|
|
266
|
+
afterPosition = row.rows[0].position;
|
|
267
|
+
}
|
|
268
|
+
const rows = await this.connection.query(`
|
|
269
|
+
SELECT
|
|
270
|
+
e.id,
|
|
271
|
+
e.aggregate_id,
|
|
272
|
+
e.aggregate_version,
|
|
273
|
+
e.type,
|
|
274
|
+
e.data,
|
|
275
|
+
e.meta,
|
|
276
|
+
e.position,
|
|
277
|
+
jsonb_object_agg(sr.saga_descriptor, sr.origin_id)
|
|
278
|
+
FILTER (WHERE sr.saga_descriptor IS NOT NULL) AS saga_origins
|
|
279
|
+
FROM ${this.#eventsTableName} e
|
|
280
|
+
LEFT JOIN ${this.#eventSagasTableName} sr
|
|
281
|
+
ON sr.event_id = e.id
|
|
282
|
+
WHERE e.position > $1
|
|
283
|
+
AND e.type = ANY($2::text[])
|
|
284
|
+
GROUP BY e.id, e.aggregate_id, e.aggregate_version, e.type, e.data, e.meta, e.position
|
|
285
|
+
ORDER BY e.position
|
|
286
|
+
`, [afterPosition, eventTypes]);
|
|
287
|
+
for (const row of rows.rows)
|
|
288
|
+
yield reconstructEvent(row);
|
|
289
|
+
}
|
|
290
|
+
async process(batch) {
|
|
291
|
+
const events = [];
|
|
292
|
+
for (const item of batch) {
|
|
293
|
+
if (!item.event)
|
|
294
|
+
throw new Error('Event batch does not contain `event`');
|
|
295
|
+
events.push(item.event);
|
|
296
|
+
}
|
|
297
|
+
await this.commitEvents(events, {
|
|
298
|
+
ignoreConcurrencyError: batch[0]?.ignoreConcurrencyError,
|
|
299
|
+
meta: extractMeta(batch[0])
|
|
300
|
+
});
|
|
301
|
+
return batch;
|
|
302
|
+
}
|
|
303
|
+
isAggregateVersionUniqueViolation(error) {
|
|
304
|
+
return typeof error === 'object' &&
|
|
305
|
+
error !== null &&
|
|
306
|
+
'code' in error &&
|
|
307
|
+
error.code === '23505' &&
|
|
308
|
+
error.constraint === this.#aggregateVersionIndexName;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=PostgresqlEventStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresqlEventStorage.js","sourceRoot":"","sources":["../../../src/postgresql/PostgresqlEventStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAmCnD,SAAS,WAAW,CAAC,QAAkC;IACtD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,CAAC;IAC7E,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QACjC,OAAO,IAAI,CAAC;IAEb,OAAO,IAAI,CAAC;AACb,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAChC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAC5B,OAAO,KAAK,CAAC;IAEd,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,WAAW,CAAC,SAAiB;IACrC,OAAO,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAa;IACtC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAuC,CAAC;IACvE,MAAM,KAAK,GAAW;QACrB,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,GAAG,IAAI;KACP,CAAC;IAEF,IAAI,GAAG,CAAC,YAAY;QACnB,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,YAAY,CAA2B,CAAC;IAE3E,OAAO,KAAK,CAAC;AACd,CAAC;AAED,MAAM,OAAO,sBAAuB,SAAQ,0BAA0B;IAKrE,MAAM,CAAU,YAAY,GAAG,YAAY,CAAC;IAC5C,MAAM,CAAU,iBAAiB,GAAG,iBAAiB,CAAC;IAE7C,gBAAgB,CAAS;IACzB,oBAAoB,CAAS;IAC7B,0BAA0B,CAAS;IAE5C,YAAY,EACX,4BAA4B,EAC5B,qBAAqB,EACrB,4BAA4B,EAM3B;QACD,KAAK,CAAC,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,CAAC,CAAC;QAE/D,MAAM,eAAe,GAAG,4BAA4B,EAAE,eAAe,IAAI,sBAAsB,CAAC,YAAY,CAAC;QAC7G,MAAM,mBAAmB,GAAG,4BAA4B,EAAE,mBAAmB;YAC5E,sBAAsB,CAAC,iBAAiB,CAAC;QAC1C,YAAY,CAAC,eAAe,EAAE,8CAA8C,CAAC,CAAC;QAC9E,YAAY,CAAC,mBAAmB,EAAE,kDAAkD,CAAC,CAAC;QAEtF,IAAI,CAAC,gBAAgB,GAAG,eAAe,CAAC,eAAe,CAAC,CAAC;QACzD,IAAI,CAAC,oBAAoB,GAAG,eAAe,CAAC,mBAAmB,CAAC,CAAC;QACjE,IAAI,CAAC,0BAA0B,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,gBAAgB,CAAC,+BAA+B,CAAC;IACxG,CAAC;IAEkB,KAAK,CAAC,UAAU,CAAC,EAAwB;QAC3D,MAAM,EAAE,CAAC,KAAK,CAAC;gCACe,IAAI,CAAC,gBAAgB;;;;;;;;;;GAUlD,CAAC,CAAC;QAEH,MAAM,EAAE,CAAC,KAAK,CAAC;gCACe,IAAI,CAAC,oBAAoB;;;;;wCAKjB,IAAI,CAAC,gBAAgB;;GAE1D,CAAC,CAAC;QAEH,MAAM,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC7D,MAAM,gBAAgB,GAAG,WAAW,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAEhE,MAAM,EAAE,CAAC,KAAK,CAAC;iCACgB,iBAAiB;QAC1C,IAAI,CAAC,gBAAgB;GAC1B,CAAC,CAAC;QAEH,wGAAwG;QACxG,MAAM,EAAE,CAAC,KAAK,CAAC;wCACuB,IAAI,CAAC,0BAA0B;QAC/D,IAAI,CAAC,gBAAgB;;;;GAI1B,CAAC,CAAC;QAEH,MAAM,EAAE,CAAC,KAAK,CAAC;iCACgB,iBAAiB;QAC1C,IAAI,CAAC,gBAAgB;GAC1B,CAAC,CAAC;QAEH,MAAM,EAAE,CAAC,KAAK,CAAC;iCACgB,gBAAgB;QACzC,IAAI,CAAC,oBAAoB;GAC9B,CAAC,CAAC;IACJ,CAAC;IAED,kDAAkD;IAClD,QAAQ;QACP,OAAO,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAiB,EAAE,OAGrC;QACA,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;gBACtC,MAAM,QAAQ,GAAG,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBACrE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;oBAC5B,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG,SAAS,EAAE,GAAG,KAAK,CAAC;oBAChD,MAAM,OAAO,GAAG,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACtC,YAAY,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;oBACjC,KAAgB,CAAC,EAAE,GAAG,OAAO,CAAC;oBAE/B,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;oBACb,IAAI,CAAC,gBAAgB;;;;;;;;;;MAUnC,EAAE;wBACF,OAAO;wBACP,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;wBAClE,KAAK,CAAC,gBAAgB,IAAI,IAAI;wBAC9B,KAAK,CAAC,IAAI;wBACV,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;wBACzB,QAAQ;wBACR,CAAC,OAAO,EAAE,sBAAsB;qBAChC,CAAC,CAAC;oBAEH,IAAI,WAAW,EAAE,CAAC;wBACjB,KAAK,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;4BAClE,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;sBACb,IAAI,CAAC,oBAAoB;;;;;;QAMvC,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;wBAC7C,CAAC;oBACF,CAAC;gBACF,CAAC;YACF,CAAC,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;YACd,IAAI,IAAI,CAAC,iCAAiC,CAAC,KAAK,CAAC;gBAChD,MAAM,IAAI,gBAAgB,CAAC,+CAA+C,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YAE/F,MAAM,KAAK,CAAC;QACb,CAAC;QAED,OAAO,MAAM,CAAC;IACf,CAAC;IAED,KAAK,CAAA,CAAE,kBAAkB,CAAC,WAAuB,EAAE,OAAoC;QACtF,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAE9B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAW;;;WAG1C,IAAI,CAAC,gBAAgB;;;;;;;;;;;;;;;;UAgBtB,IAAI,CAAC,gBAAgB;;eAEhB,IAAI,CAAC,oBAAoB;;;;;;;;;;;GAWrC,EAAE;YACF,MAAM,CAAC,WAAW,CAAC;YACnB,OAAO,EAAE,QAAQ,EAAE,gBAAgB,IAAI,IAAI;YAC3C,OAAO,EAAE,UAAU,IAAI,IAAI;YAC3B,OAAO,EAAE,IAAI,IAAI,IAAI;SACrB,CAAC,CAAC;QAEH,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI;YAC1B,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAA,CAAE,aAAa,CAAC,MAAkB,EAAE,EAAE,WAAW,EAA2B;QAChF,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAE9B,YAAY,CAAC,WAAW,EAAE,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAEhD,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QAC9D,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,KAAK,aAAa;YAC9D,MAAM,IAAI,SAAS,CAAC,+CAA+C,CAAC,CAAC;QAEtE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAe;;;6BAG5B,IAAI,CAAC,gBAAgB;6BACrB,IAAI,CAAC,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;WAyBvC,IAAI,CAAC,gBAAgB;gBAChB,IAAI,CAAC,oBAAoB;;;;gBAIzB,IAAI,CAAC,oBAAoB;;;;;;;;;;;GAWtC,EAAE,CAAC,cAAc,EAAE,aAAa,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;QAEpD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,eAAe;YAC3B,MAAM,IAAI,KAAK,CAAC,gBAAgB,aAAa,YAAY,CAAC,CAAC;QAE5D,IAAI,CAAC,MAAM,CAAC,eAAe;YAC1B,MAAM,IAAI,KAAK,CAAC,eAAe,WAAW,CAAC,EAAE,YAAY,CAAC,CAAC;QAE5D,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAI,GAAG,CAAC,EAAE;gBACT,MAAM,gBAAgB,CAAC,GAAe,CAAC,CAAC;QAC1C,CAAC;IACF,CAAC;IAED,KAAK,CAAA,CAAE,gBAAgB,CAAC,UAA8B,EAAE,OAAyB;QAChF,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAE9B,MAAM,WAAW,GAAG,OAAO,EAAE,UAAU,EAAE,EAAE,CAAC;QAC5C,IAAI,OAAO,EAAE,UAAU;YACtB,YAAY,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,uBAAuB,CAAC,CAAC;QAE9D,IAAI,aAAa,GAAoB,CAAC,CAAC;QACvC,IAAI,WAAW,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAc;;WAE7C,IAAI,CAAC,gBAAgB;;IAE5B,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;YAElB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM;gBACnB,OAAO;YAER,aAAa,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACtC,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,KAAK,CAAW;;;;;;;;;;;UAW3C,IAAI,CAAC,gBAAgB;eAChB,IAAI,CAAC,oBAAoB;;;;;;GAMrC,EAAE,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;QAEhC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI;YAC1B,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAA4B;QACzC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,KAAK;gBACd,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;YAEzD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;QAED,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE;YAC/B,sBAAsB,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,sBAAsB;YACxD,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAC3B,CAAC,CAAC;QAEH,OAAO,KAAK,CAAC;IACd,CAAC;IAEO,iCAAiC,CAAC,KAAc;QACvD,OAAO,OAAO,KAAK,KAAK,QAAQ;YAC/B,KAAK,KAAK,IAAI;YACd,MAAM,IAAI,KAAK;YACd,KAAyB,CAAC,IAAI,KAAK,OAAO;YAC1C,KAAyB,CAAC,UAAU,KAAK,IAAI,CAAC,0BAA0B,CAAC;IAC5E,CAAC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { assertDefined, assertFunction, assertNonNegativeInteger, assertString } from "../utils/assert.js";
|
|
2
|
+
import { AbstractPostgresqlAccessor } from "./AbstractPostgresqlAccessor.js";
|
|
3
|
+
import { quoteIdentifier } from "./utils/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* PostgreSQL-backed implementation of IObjectStorage.
|
|
6
|
+
*
|
|
7
|
+
* Each record is stored as a row `{ id, data, version }`.
|
|
8
|
+
* The version field enables optimistic concurrency control: `update` and
|
|
9
|
+
* `updateEnforcingNew` re-read the record after the user callback runs and
|
|
10
|
+
* atomically commit only when the version still matches.
|
|
11
|
+
* On mismatch the operation retries up to `maxRetries` times.
|
|
12
|
+
*/
|
|
13
|
+
export class PostgresqlObjectStorage extends AbstractPostgresqlAccessor {
|
|
14
|
+
#tableName;
|
|
15
|
+
#maxRetries;
|
|
16
|
+
constructor(o) {
|
|
17
|
+
super(o);
|
|
18
|
+
assertString(o.tableName, 'tableName');
|
|
19
|
+
if (o.maxRetries !== undefined)
|
|
20
|
+
assertNonNegativeInteger(o.maxRetries, 'maxRetries');
|
|
21
|
+
this.#tableName = quoteIdentifier(o.tableName);
|
|
22
|
+
this.#maxRetries = o.maxRetries ?? 100;
|
|
23
|
+
}
|
|
24
|
+
async initialize(db) {
|
|
25
|
+
await db.query(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS ${this.#tableName} (
|
|
27
|
+
id text PRIMARY KEY,
|
|
28
|
+
version integer NOT NULL DEFAULT 1,
|
|
29
|
+
data jsonb NOT NULL
|
|
30
|
+
)
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
async get(id) {
|
|
34
|
+
assertDefined(id, 'id');
|
|
35
|
+
await this.assertConnection();
|
|
36
|
+
const result = await this.connection.query(`
|
|
37
|
+
SELECT data, version
|
|
38
|
+
FROM ${this.#tableName}
|
|
39
|
+
WHERE id = $1
|
|
40
|
+
`, [String(id)]);
|
|
41
|
+
const row = result.rows[0];
|
|
42
|
+
if (!row)
|
|
43
|
+
return undefined;
|
|
44
|
+
return PostgresqlObjectStorage.parseData(row.data);
|
|
45
|
+
}
|
|
46
|
+
async create(id, data) {
|
|
47
|
+
assertDefined(id, 'id');
|
|
48
|
+
await this.assertConnection();
|
|
49
|
+
const result = await this.connection.query(`
|
|
50
|
+
INSERT INTO ${this.#tableName} (id, data)
|
|
51
|
+
VALUES ($1, $2::jsonb)
|
|
52
|
+
ON CONFLICT (id) DO NOTHING
|
|
53
|
+
`, [String(id), JSON.stringify(data)]);
|
|
54
|
+
if (result.rowCount !== 1)
|
|
55
|
+
throw new Error(`Record '${id}' could not be created`);
|
|
56
|
+
}
|
|
57
|
+
async update(id, update) {
|
|
58
|
+
assertDefined(id, 'id');
|
|
59
|
+
assertFunction(update, 'update');
|
|
60
|
+
await this.assertConnection();
|
|
61
|
+
for (let attempt = 0; attempt <= this.#maxRetries; attempt++) {
|
|
62
|
+
const row = await this.getRow(id);
|
|
63
|
+
if (!row)
|
|
64
|
+
throw new Error(`Record '${id}' does not exist`);
|
|
65
|
+
const updatedData = update(PostgresqlObjectStorage.parseData(row.data));
|
|
66
|
+
const result = await this.updateByVersion(id, row.version, updatedData);
|
|
67
|
+
if (result.rowCount === 1)
|
|
68
|
+
return;
|
|
69
|
+
// version mismatch - retry
|
|
70
|
+
}
|
|
71
|
+
throw new Error(`Record '${id}' could not be updated after ${this.#maxRetries} retries`);
|
|
72
|
+
}
|
|
73
|
+
async updateEnforcingNew(id, update) {
|
|
74
|
+
assertDefined(id, 'id');
|
|
75
|
+
assertFunction(update, 'update');
|
|
76
|
+
await this.assertConnection();
|
|
77
|
+
for (let attempt = 0; attempt <= this.#maxRetries; attempt++) {
|
|
78
|
+
const row = await this.getRow(id);
|
|
79
|
+
if (row) {
|
|
80
|
+
const updatedData = update(PostgresqlObjectStorage.parseData(row.data));
|
|
81
|
+
const result = await this.updateByVersion(id, row.version, updatedData);
|
|
82
|
+
if (result.rowCount === 1)
|
|
83
|
+
return;
|
|
84
|
+
// version mismatch - retry
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const result = await this.connection.query(`
|
|
88
|
+
INSERT INTO ${this.#tableName} (id, data)
|
|
89
|
+
VALUES ($1, $2::jsonb)
|
|
90
|
+
ON CONFLICT (id) DO NOTHING
|
|
91
|
+
`, [String(id), JSON.stringify(update(undefined))]);
|
|
92
|
+
if (result.rowCount === 1)
|
|
93
|
+
return;
|
|
94
|
+
// Another process inserted first - retry
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`Record '${id}' could not be upserted after ${this.#maxRetries} retries`);
|
|
98
|
+
}
|
|
99
|
+
async delete(id) {
|
|
100
|
+
assertDefined(id, 'id');
|
|
101
|
+
await this.assertConnection();
|
|
102
|
+
const result = await this.connection.query(`
|
|
103
|
+
DELETE FROM ${this.#tableName}
|
|
104
|
+
WHERE id = $1
|
|
105
|
+
`, [String(id)]);
|
|
106
|
+
return result.rowCount === 1;
|
|
107
|
+
}
|
|
108
|
+
async getRow(id) {
|
|
109
|
+
const result = await this.connection.query(`
|
|
110
|
+
SELECT data, version
|
|
111
|
+
FROM ${this.#tableName}
|
|
112
|
+
WHERE id = $1
|
|
113
|
+
`, [String(id)]);
|
|
114
|
+
return result.rows[0];
|
|
115
|
+
}
|
|
116
|
+
updateByVersion(id, version, data) {
|
|
117
|
+
return this.connection.query(`
|
|
118
|
+
UPDATE ${this.#tableName}
|
|
119
|
+
SET
|
|
120
|
+
data = $1::jsonb,
|
|
121
|
+
version = version + 1
|
|
122
|
+
WHERE
|
|
123
|
+
id = $2
|
|
124
|
+
AND version = $3
|
|
125
|
+
`, [JSON.stringify(data), String(id), version]);
|
|
126
|
+
}
|
|
127
|
+
static parseData(data) {
|
|
128
|
+
if (typeof data === 'string')
|
|
129
|
+
return JSON.parse(data);
|
|
130
|
+
return data;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=PostgresqlObjectStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresqlObjectStorage.js","sourceRoot":"","sources":["../../../src/postgresql/PostgresqlObjectStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { assertString } from "../utils/assert.js";
|
|
2
|
+
import { AbstractPostgresqlView } from "./AbstractPostgresqlView.js";
|
|
3
|
+
import { PostgresqlObjectStorage } from "./PostgresqlObjectStorage.js";
|
|
4
|
+
/**
|
|
5
|
+
* PostgreSQL-backed object view with restore locking and last-processed-event tracking.
|
|
6
|
+
*/
|
|
7
|
+
export class PostgresqlObjectView extends AbstractPostgresqlView {
|
|
8
|
+
#postgresqlObjectStorage;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
assertString(options?.tableNamePrefix, 'tableNamePrefix');
|
|
11
|
+
assertString(options?.schemaVersion, 'schemaVersion');
|
|
12
|
+
super(options);
|
|
13
|
+
this.#postgresqlObjectStorage = new PostgresqlObjectStorage({
|
|
14
|
+
viewModelPostgresqlDb: options.viewModelPostgresqlDb,
|
|
15
|
+
viewModelPostgresqlDbFactory: options.viewModelPostgresqlDbFactory,
|
|
16
|
+
tableName: `${options.tableNamePrefix}_${options.schemaVersion}`,
|
|
17
|
+
maxRetries: options.postgresqlObjectStorageMaxRetries
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
// eslint-disable-next-line class-methods-use-this
|
|
21
|
+
initialize(_db) {
|
|
22
|
+
// No need to initialize the table here, it's done in PostgresqlObjectStorage
|
|
23
|
+
}
|
|
24
|
+
async get(id) {
|
|
25
|
+
if (!this.ready)
|
|
26
|
+
await this.once('ready');
|
|
27
|
+
return this.#postgresqlObjectStorage.get(id);
|
|
28
|
+
}
|
|
29
|
+
async create(id, data) {
|
|
30
|
+
await this.#postgresqlObjectStorage.create(id, data);
|
|
31
|
+
}
|
|
32
|
+
async update(id, update) {
|
|
33
|
+
await this.#postgresqlObjectStorage.update(id, update);
|
|
34
|
+
}
|
|
35
|
+
async updateEnforcingNew(id, update) {
|
|
36
|
+
await this.#postgresqlObjectStorage.updateEnforcingNew(id, update);
|
|
37
|
+
}
|
|
38
|
+
async delete(id) {
|
|
39
|
+
return this.#postgresqlObjectStorage.delete(id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=PostgresqlObjectView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresqlObjectView.js","sourceRoot":"","sources":["../../../src/postgresql/PostgresqlObjectView.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAGvE;;GAEG;AACH,MAAM,OAAO,oBACZ,SAAQ,sBAAsB;IAGrB,wBAAwB,CAAmC;IAEpE,YAAY,OAGX;QACA,YAAY,CAAC,OAAO,EAAE,eAAe,EAAE,iBAAiB,CAAC,CAAC;QAC1D,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,eAAe,CAAC,CAAC;QAEtD,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,wBAAwB,GAAG,IAAI,uBAAuB,CAAU;YACpE,qBAAqB,EAAE,OAAO,CAAC,qBAAqB;YACpD,4BAA4B,EAAE,OAAO,CAAC,4BAA4B;YAClE,SAAS,EAAE,GAAG,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,aAAa,EAAE;YAChE,UAAU,EAAE,OAAO,CAAC,iCAAiC;SACrD,CAAC,CAAC;IACJ,CAAC;IAED,kDAAkD;IACxC,UAAU,CAAC,GAAyB;QAC7C,6EAA6E;IAC9E,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAc;QACvB,IAAI,CAAC,IAAI,CAAC,KAAK;YACd,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE1B,OAAO,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAc,EAAE,IAAa;QACzC,MAAM,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAc,EAAE,MAA+B;QAC3D,MAAM,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,EAAc,EAAE,MAAgC;QACxE,MAAM,IAAI,CAAC,wBAAwB,CAAC,kBAAkB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAc;QAC1B,OAAO,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC;CACD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresqlProjectionDataParams.js","sourceRoot":"","sources":["../../../src/postgresql/PostgresqlProjectionDataParams.ts"],"names":[],"mappings":""}
|