node-cqrs 1.2.0-beta.1 → 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 +5 -17
- 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,147 @@
|
|
|
1
|
+
import { assertNonNegativeInteger, assertString, Deferred } from "../utils/index.js";
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { AbstractPostgresqlAccessor } from "./AbstractPostgresqlAccessor.js";
|
|
5
|
+
import { quoteIdentifier } from "./utils/index.js";
|
|
6
|
+
const delay = promisify(setTimeout);
|
|
7
|
+
/**
|
|
8
|
+
* PostgreSQL-backed implementation of IViewLocker.
|
|
9
|
+
*
|
|
10
|
+
* Uses a row with token + locked_till semantics to acquire a distributed view lock.
|
|
11
|
+
* The lock is automatically prolonged at half the TTL interval to prevent expiration
|
|
12
|
+
* while processing is in progress.
|
|
13
|
+
*/
|
|
14
|
+
export class PostgresqlViewLocker extends AbstractPostgresqlAccessor {
|
|
15
|
+
static DEFAULT_VIEW_LOCK_TTL = 120_000;
|
|
16
|
+
static DEFAULT_TABLE = 'ncqrs_view_locks';
|
|
17
|
+
#projectionName;
|
|
18
|
+
#schemaVersion;
|
|
19
|
+
#viewLockTableName;
|
|
20
|
+
#viewLockTtl;
|
|
21
|
+
#logger;
|
|
22
|
+
#lockToken;
|
|
23
|
+
#lockMarker;
|
|
24
|
+
#lockProlongationTimeout;
|
|
25
|
+
constructor(o) {
|
|
26
|
+
super(o);
|
|
27
|
+
assertString(o.projectionName, 'o.projectionName');
|
|
28
|
+
assertString(o.schemaVersion, 'o.schemaVersion');
|
|
29
|
+
if (o.viewLockTableName !== undefined)
|
|
30
|
+
assertString(o.viewLockTableName, 'o.viewLockTableName');
|
|
31
|
+
if (o.viewLockTtl !== undefined)
|
|
32
|
+
assertNonNegativeInteger(o.viewLockTtl, 'o.viewLockTtl');
|
|
33
|
+
this.#projectionName = o.projectionName;
|
|
34
|
+
this.#schemaVersion = o.schemaVersion;
|
|
35
|
+
this.#viewLockTableName = quoteIdentifier(o.viewLockTableName ?? PostgresqlViewLocker.DEFAULT_TABLE);
|
|
36
|
+
this.#viewLockTtl = o.viewLockTtl ?? PostgresqlViewLocker.DEFAULT_VIEW_LOCK_TTL;
|
|
37
|
+
this.#logger = o.logger && 'child' in o.logger ?
|
|
38
|
+
o.logger.child({ service: this.constructor.name }) :
|
|
39
|
+
o.logger;
|
|
40
|
+
}
|
|
41
|
+
async initialize(db) {
|
|
42
|
+
await db.query(`
|
|
43
|
+
CREATE TABLE IF NOT EXISTS ${this.#viewLockTableName} (
|
|
44
|
+
projection_name text NOT NULL,
|
|
45
|
+
schema_version text NOT NULL,
|
|
46
|
+
locked_till timestamptz NULL,
|
|
47
|
+
lock_token text NULL,
|
|
48
|
+
last_event text NULL,
|
|
49
|
+
PRIMARY KEY (projection_name, schema_version)
|
|
50
|
+
)
|
|
51
|
+
`);
|
|
52
|
+
await db.query(`
|
|
53
|
+
CREATE INDEX IF NOT EXISTS ${quoteIdentifier(`${this.#tableNameForIndex()}_locked_till_idx`)}
|
|
54
|
+
ON ${this.#viewLockTableName} (locked_till)
|
|
55
|
+
`);
|
|
56
|
+
}
|
|
57
|
+
get ready() {
|
|
58
|
+
return !this.#lockMarker;
|
|
59
|
+
}
|
|
60
|
+
async lock() {
|
|
61
|
+
this.#lockMarker = new Deferred();
|
|
62
|
+
this.#lockToken = randomUUID();
|
|
63
|
+
await this.assertConnection();
|
|
64
|
+
let lockAcquired = false;
|
|
65
|
+
while (!lockAcquired) {
|
|
66
|
+
const lockedTill = new Date(Date.now() + this.#viewLockTtl);
|
|
67
|
+
const upsertResult = await this.connection.query(`
|
|
68
|
+
INSERT INTO ${this.#viewLockTableName} AS view_lock
|
|
69
|
+
(projection_name, schema_version, locked_till, lock_token)
|
|
70
|
+
VALUES ($1, $2, $3, $4)
|
|
71
|
+
ON CONFLICT (projection_name, schema_version)
|
|
72
|
+
DO UPDATE SET
|
|
73
|
+
locked_till = excluded.locked_till,
|
|
74
|
+
lock_token = excluded.lock_token
|
|
75
|
+
WHERE
|
|
76
|
+
view_lock.locked_till IS NULL
|
|
77
|
+
OR view_lock.locked_till < NOW()
|
|
78
|
+
`, [this.#projectionName, this.#schemaVersion, lockedTill, this.#lockToken]);
|
|
79
|
+
lockAcquired = upsertResult.rowCount === 1;
|
|
80
|
+
if (!lockAcquired) {
|
|
81
|
+
this.#logger?.debug(`"${this.#projectionName}" is locked by another process`);
|
|
82
|
+
await delay(this.#viewLockTtl / 2);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
this.#logger?.debug(`"${this.#projectionName}" lock obtained for ${this.#viewLockTtl}ms`);
|
|
86
|
+
this.scheduleLockProlongation();
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
scheduleLockProlongation() {
|
|
90
|
+
const ms = this.#viewLockTtl / 2;
|
|
91
|
+
this.#lockProlongationTimeout = setTimeout(() => this.prolongLock(), ms);
|
|
92
|
+
this.#lockProlongationTimeout.unref();
|
|
93
|
+
this.#logger?.debug(`"${this.#projectionName}" lock refresh scheduled in ${ms}ms`);
|
|
94
|
+
}
|
|
95
|
+
cancelLockProlongation() {
|
|
96
|
+
clearTimeout(this.#lockProlongationTimeout);
|
|
97
|
+
this.#logger?.debug(`"${this.#projectionName}" lock refresh canceled`);
|
|
98
|
+
}
|
|
99
|
+
async prolongLock() {
|
|
100
|
+
await this.assertConnection();
|
|
101
|
+
const lockedTill = new Date(Date.now() + this.#viewLockTtl);
|
|
102
|
+
const result = await this.connection.query(`
|
|
103
|
+
UPDATE ${this.#viewLockTableName}
|
|
104
|
+
SET
|
|
105
|
+
locked_till = $1
|
|
106
|
+
WHERE
|
|
107
|
+
projection_name = $2
|
|
108
|
+
AND schema_version = $3
|
|
109
|
+
AND lock_token = $4
|
|
110
|
+
AND locked_till IS NOT NULL
|
|
111
|
+
`, [lockedTill, this.#projectionName, this.#schemaVersion, this.#lockToken]);
|
|
112
|
+
if (result.rowCount !== 1)
|
|
113
|
+
throw new Error(`"${this.#projectionName}" lock could not be prolonged`);
|
|
114
|
+
this.#logger?.debug(`"${this.#projectionName}" lock prolonged for ${this.#viewLockTtl}ms`);
|
|
115
|
+
this.scheduleLockProlongation();
|
|
116
|
+
}
|
|
117
|
+
async unlock() {
|
|
118
|
+
this.#lockMarker?.resolve();
|
|
119
|
+
this.#lockMarker = undefined;
|
|
120
|
+
this.cancelLockProlongation();
|
|
121
|
+
await this.assertConnection();
|
|
122
|
+
const result = await this.connection.query(`
|
|
123
|
+
UPDATE ${this.#viewLockTableName}
|
|
124
|
+
SET
|
|
125
|
+
locked_till = NULL,
|
|
126
|
+
lock_token = NULL
|
|
127
|
+
WHERE
|
|
128
|
+
projection_name = $1
|
|
129
|
+
AND schema_version = $2
|
|
130
|
+
AND lock_token = $3
|
|
131
|
+
`, [this.#projectionName, this.#schemaVersion, this.#lockToken]);
|
|
132
|
+
this.#lockToken = undefined;
|
|
133
|
+
if (result.rowCount === 1)
|
|
134
|
+
this.#logger?.debug(`"${this.#projectionName}" lock released`);
|
|
135
|
+
else
|
|
136
|
+
this.#logger?.warn(`"${this.#projectionName}" lock didn't exist`);
|
|
137
|
+
}
|
|
138
|
+
once(event) {
|
|
139
|
+
if (event !== 'ready')
|
|
140
|
+
throw new TypeError(`Unexpected event: ${event}`);
|
|
141
|
+
return this.#lockMarker?.promise ?? Promise.resolve();
|
|
142
|
+
}
|
|
143
|
+
#tableNameForIndex() {
|
|
144
|
+
return this.#viewLockTableName.replaceAll('"', '').replaceAll('.', '_');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=PostgresqlViewLocker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PostgresqlViewLocker.js","sourceRoot":"","sources":["../../../src/postgresql/PostgresqlViewLocker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import "./IContainer.js";
|
|
2
|
+
export * from "./AbstractPostgresqlAccessor.js";
|
|
3
|
+
export * from "./AbstractPostgresqlObjectProjection.js";
|
|
4
|
+
export * from "./AbstractPostgresqlView.js";
|
|
5
|
+
export * from "./PostgresqlConnection.js";
|
|
6
|
+
export * from "./PostgresqlEventStorage.js";
|
|
7
|
+
export * from "./PostgresqlEventLocker.js";
|
|
8
|
+
export * from "./PostgresqlObjectStorage.js";
|
|
9
|
+
export * from "./PostgresqlObjectView.js";
|
|
10
|
+
export * from "./PostgresqlProjectionDataParams.js";
|
|
11
|
+
export * from "./PostgresqlViewLocker.js";
|
|
12
|
+
export * from "./utils/index.js";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/postgresql/index.ts"],"names":[],"mappings":"AAAA,OAAO,iBAAiB,CAAC;AAEzB,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Get assigned or generate a deterministic event ID as a hex string.
|
|
4
|
+
*/
|
|
5
|
+
export const getEventId = (event) => {
|
|
6
|
+
if (typeof event.id === 'string')
|
|
7
|
+
return event.id;
|
|
8
|
+
return createHash('md5').update(JSON.stringify(event)).digest('hex');
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=getEventId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEventId.js","sourceRoot":"","sources":["../../../../src/postgresql/utils/getEventId.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE;IACnD,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ;QAC/B,OAAO,KAAK,CAAC,EAAE,CAAC;IAEjB,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/postgresql/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { assertString } from "../../utils/assert.js";
|
|
2
|
+
const IDENTIFIER_PART_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3
|
+
export function quoteIdentifier(identifier) {
|
|
4
|
+
assertString(identifier, 'identifier');
|
|
5
|
+
return identifier.split('.').map(part => {
|
|
6
|
+
if (!IDENTIFIER_PART_PATTERN.test(part))
|
|
7
|
+
throw new TypeError(`Invalid PostgreSQL identifier: ${identifier}`);
|
|
8
|
+
return `"${part}"`;
|
|
9
|
+
}).join('.');
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=quoteIdentifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quoteIdentifier.js","sourceRoot":"","sources":["../../../../src/postgresql/utils/quoteIdentifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,uBAAuB,GAAG,0BAA0B,CAAC;AAE3D,MAAM,UAAU,eAAe,CAAC,UAAkB;IACjD,YAAY,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAEvC,OAAO,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QACvC,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC;YACtC,MAAM,IAAI,SAAS,CAAC,kCAAkC,UAAU,EAAE,CAAC,CAAC;QAErE,OAAO,IAAI,IAAI,GAAG,CAAC;IACpB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC"}
|
|
@@ -9,6 +9,16 @@ function extractMeta(envelope) {
|
|
|
9
9
|
return null;
|
|
10
10
|
return rest;
|
|
11
11
|
}
|
|
12
|
+
function reconstructEvent(row) {
|
|
13
|
+
const data = JSON.parse(row.data);
|
|
14
|
+
const event = {
|
|
15
|
+
id: bufferToGuid(row.id),
|
|
16
|
+
...data
|
|
17
|
+
};
|
|
18
|
+
if (row.saga_origins)
|
|
19
|
+
event.sagaOrigins = JSON.parse(row.saga_origins);
|
|
20
|
+
return event;
|
|
21
|
+
}
|
|
12
22
|
export class SqliteEventStorage extends AbstractSqliteAccessor {
|
|
13
23
|
#insertEventQuery;
|
|
14
24
|
#insertSagaRefQuery;
|
|
@@ -16,7 +26,6 @@ export class SqliteEventStorage extends AbstractSqliteAccessor {
|
|
|
16
26
|
#getRowidQuery;
|
|
17
27
|
#getAggregateEventsQuery;
|
|
18
28
|
#getSagaEventsQuery;
|
|
19
|
-
#getSagaRefsQuery;
|
|
20
29
|
#getEventsByTypesQuery;
|
|
21
30
|
initialize(db) {
|
|
22
31
|
db.pragma('foreign_keys = ON');
|
|
@@ -62,8 +71,23 @@ export class SqliteEventStorage extends AbstractSqliteAccessor {
|
|
|
62
71
|
ORDER BY rowid DESC
|
|
63
72
|
LIMIT 1
|
|
64
73
|
)
|
|
65
|
-
SELECT
|
|
66
|
-
|
|
74
|
+
SELECT
|
|
75
|
+
e.id,
|
|
76
|
+
e.aggregate_id,
|
|
77
|
+
e.aggregate_version,
|
|
78
|
+
e.type,
|
|
79
|
+
e.data,
|
|
80
|
+
e.meta,
|
|
81
|
+
e.rowid,
|
|
82
|
+
NULLIF(
|
|
83
|
+
json_group_object(sr.saga_descriptor, lower(hex(sr.origin_id)))
|
|
84
|
+
FILTER (WHERE sr.saga_descriptor IS NOT NULL),
|
|
85
|
+
'{}'
|
|
86
|
+
) AS saga_origins
|
|
87
|
+
FROM tbl_events e
|
|
88
|
+
CROSS JOIN tail
|
|
89
|
+
LEFT JOIN tbl_event_sagas sr
|
|
90
|
+
ON sr.event_id = e.id
|
|
67
91
|
WHERE e.aggregate_id = @aggregateId
|
|
68
92
|
AND (@afterVersion IS NULL OR e.aggregate_version > @afterVersion)
|
|
69
93
|
AND (
|
|
@@ -71,29 +95,55 @@ export class SqliteEventStorage extends AbstractSqliteAccessor {
|
|
|
71
95
|
OR e.type IN (SELECT value FROM json_each(@eventTypes))
|
|
72
96
|
OR (@tail = 'last' AND e.id = tail.tail_id)
|
|
73
97
|
)
|
|
98
|
+
GROUP BY e.id, e.aggregate_id, e.aggregate_version, e.type, e.data, e.meta, e.rowid
|
|
74
99
|
ORDER BY e.rowid
|
|
75
100
|
`);
|
|
76
101
|
this.#getSagaEventsQuery = db.prepare(`
|
|
77
|
-
SELECT
|
|
102
|
+
SELECT
|
|
103
|
+
e.id,
|
|
104
|
+
e.aggregate_id,
|
|
105
|
+
e.aggregate_version,
|
|
106
|
+
e.type,
|
|
107
|
+
e.data,
|
|
108
|
+
e.meta,
|
|
109
|
+
e.rowid,
|
|
110
|
+
NULLIF(
|
|
111
|
+
json_group_object(all_sr.saga_descriptor, lower(hex(all_sr.origin_id)))
|
|
112
|
+
FILTER (WHERE all_sr.saga_descriptor IS NOT NULL),
|
|
113
|
+
'{}'
|
|
114
|
+
) AS saga_origins
|
|
78
115
|
FROM tbl_events e
|
|
79
116
|
LEFT JOIN tbl_event_sagas sr
|
|
80
117
|
ON sr.event_id = e.id
|
|
81
118
|
AND sr.saga_descriptor = @sagaDescriptor
|
|
82
119
|
AND sr.origin_id = @originId
|
|
120
|
+
LEFT JOIN tbl_event_sagas all_sr
|
|
121
|
+
ON all_sr.event_id = e.id
|
|
83
122
|
WHERE e.rowid >= @originRowid AND e.rowid < @beforeRowid
|
|
84
123
|
AND (e.id = @originId OR sr.event_id IS NOT NULL)
|
|
124
|
+
GROUP BY e.id, e.aggregate_id, e.aggregate_version, e.type, e.data, e.meta, e.rowid
|
|
85
125
|
ORDER BY e.rowid
|
|
86
126
|
`);
|
|
87
|
-
this.#getSagaRefsQuery = db.prepare(`
|
|
88
|
-
SELECT saga_descriptor, origin_id
|
|
89
|
-
FROM tbl_event_sagas
|
|
90
|
-
WHERE event_id = ?
|
|
91
|
-
`);
|
|
92
127
|
this.#getEventsByTypesQuery = db.prepare(`
|
|
93
|
-
SELECT
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
128
|
+
SELECT
|
|
129
|
+
e.id,
|
|
130
|
+
e.aggregate_id,
|
|
131
|
+
e.aggregate_version,
|
|
132
|
+
e.type,
|
|
133
|
+
e.data,
|
|
134
|
+
e.meta,
|
|
135
|
+
e.rowid,
|
|
136
|
+
NULLIF(
|
|
137
|
+
json_group_object(sr.saga_descriptor, lower(hex(sr.origin_id)))
|
|
138
|
+
FILTER (WHERE sr.saga_descriptor IS NOT NULL),
|
|
139
|
+
'{}'
|
|
140
|
+
) AS saga_origins
|
|
141
|
+
FROM tbl_events e
|
|
142
|
+
LEFT JOIN tbl_event_sagas sr
|
|
143
|
+
ON sr.event_id = e.id
|
|
144
|
+
WHERE e.rowid > ?
|
|
145
|
+
GROUP BY e.id, e.aggregate_id, e.aggregate_version, e.type, e.data, e.meta, e.rowid
|
|
146
|
+
ORDER BY e.rowid
|
|
97
147
|
`);
|
|
98
148
|
}
|
|
99
149
|
// eslint-disable-next-line class-methods-use-this
|
|
@@ -134,7 +184,7 @@ export class SqliteEventStorage extends AbstractSqliteAccessor {
|
|
|
134
184
|
tail: options?.tail ?? null
|
|
135
185
|
});
|
|
136
186
|
for (const row of rows)
|
|
137
|
-
yield
|
|
187
|
+
yield reconstructEvent(row);
|
|
138
188
|
}
|
|
139
189
|
async *getSagaEvents(sagaId, { beforeEvent }) {
|
|
140
190
|
await this.assertConnection();
|
|
@@ -155,7 +205,7 @@ export class SqliteEventStorage extends AbstractSqliteAccessor {
|
|
|
155
205
|
beforeRowid: beforeRowid.rowid
|
|
156
206
|
});
|
|
157
207
|
for (const row of rows)
|
|
158
|
-
yield
|
|
208
|
+
yield reconstructEvent(row);
|
|
159
209
|
}
|
|
160
210
|
async *getEventsByTypes(eventTypes, options) {
|
|
161
211
|
await this.assertConnection();
|
|
@@ -172,7 +222,7 @@ export class SqliteEventStorage extends AbstractSqliteAccessor {
|
|
|
172
222
|
const rows = this.#getEventsByTypesQuery.all(afterRowid);
|
|
173
223
|
for (const row of rows) {
|
|
174
224
|
if (eventTypes.includes(row.type))
|
|
175
|
-
yield
|
|
225
|
+
yield reconstructEvent(row);
|
|
176
226
|
}
|
|
177
227
|
}
|
|
178
228
|
async process(batch) {
|
|
@@ -187,25 +237,5 @@ export class SqliteEventStorage extends AbstractSqliteAccessor {
|
|
|
187
237
|
await this.commitEvents(events, { ignoreConcurrencyError, meta });
|
|
188
238
|
return batch;
|
|
189
239
|
}
|
|
190
|
-
#getSagaOriginsForEvent(eventIdBuf) {
|
|
191
|
-
const refs = this.#getSagaRefsQuery.all(eventIdBuf);
|
|
192
|
-
if (refs.length === 0)
|
|
193
|
-
return {};
|
|
194
|
-
const sagaOrigins = {};
|
|
195
|
-
for (const ref of refs)
|
|
196
|
-
sagaOrigins[ref.saga_descriptor] = bufferToGuid(ref.origin_id);
|
|
197
|
-
return sagaOrigins;
|
|
198
|
-
}
|
|
199
|
-
#reconstructEvent(row) {
|
|
200
|
-
const data = JSON.parse(row.data);
|
|
201
|
-
const sagaOrigins = this.#getSagaOriginsForEvent(row.id);
|
|
202
|
-
const event = {
|
|
203
|
-
id: bufferToGuid(row.id),
|
|
204
|
-
...data
|
|
205
|
-
};
|
|
206
|
-
if (Object.keys(sagaOrigins).length > 0)
|
|
207
|
-
event.sagaOrigins = sagaOrigins;
|
|
208
|
-
return event;
|
|
209
|
-
}
|
|
210
240
|
}
|
|
211
241
|
//# sourceMappingURL=SqliteEventStorage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SqliteEventStorage.js","sourceRoot":"","sources":["../../../src/sqlite/SqliteEventStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"SqliteEventStorage.js","sourceRoot":"","sources":["../../../src/sqlite/SqliteEventStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IContainer } from 'node-cqrs';
|
|
2
|
+
import type { PostgresqlConnection } from './PostgresqlConnection.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for accessing a PostgreSQL connection.
|
|
5
|
+
*
|
|
6
|
+
* Manages the connection lifecycle, ensuring initialization via `assertConnection`.
|
|
7
|
+
* Supports providing a query-capable connection directly or a factory function for lazy initialization.
|
|
8
|
+
*
|
|
9
|
+
* Subclasses must implement the `initialize` method for specific setup tasks.
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class AbstractPostgresqlAccessor {
|
|
12
|
+
#private;
|
|
13
|
+
protected db: PostgresqlConnection | undefined;
|
|
14
|
+
constructor(c: Partial<Pick<IContainer, 'viewModelPostgresqlDb' | 'viewModelPostgresqlDbFactory'>>);
|
|
15
|
+
protected abstract initialize(db: PostgresqlConnection): Promise<void> | void;
|
|
16
|
+
protected get connection(): PostgresqlConnection;
|
|
17
|
+
/**
|
|
18
|
+
* Ensures that the PostgreSQL connection is initialized.
|
|
19
|
+
* Uses a lock to prevent race conditions during concurrent initialization attempts.
|
|
20
|
+
* If the connection is not already set, it creates one using the provided factory
|
|
21
|
+
* and then calls the `initialize` method.
|
|
22
|
+
*
|
|
23
|
+
* This method is idempotent and safe to call multiple times.
|
|
24
|
+
*/
|
|
25
|
+
assertConnection(): Promise<void>;
|
|
26
|
+
runInTransaction<T>(callback: () => Promise<T> | T): Promise<T>;
|
|
27
|
+
private getTransactionConnection;
|
|
28
|
+
private static isConnectionPool;
|
|
29
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IContainer } from 'node-cqrs';
|
|
2
|
+
import { AbstractProjection } from '../AbstractProjection.ts';
|
|
3
|
+
import type { IEvent } from '../interfaces/index.ts';
|
|
4
|
+
import { PostgresqlObjectView } from './PostgresqlObjectView.ts';
|
|
5
|
+
type PostgresqlObjectProjectionParams = Partial<Pick<IContainer, 'viewModelPostgresqlDb' | 'viewModelPostgresqlDbFactory' | 'logger' | 'postgresqlObjectStorageMaxRetries'>> & Partial<Pick<ConstructorParameters<typeof PostgresqlObjectView>[0], 'eventLockTableName' | 'eventLockTtl' | 'viewLockTableName' | 'viewLockTtl'>>;
|
|
6
|
+
export declare abstract class AbstractPostgresqlObjectProjection<T> extends AbstractProjection<PostgresqlObjectView<T>> {
|
|
7
|
+
static get tableName(): string;
|
|
8
|
+
static get schemaVersion(): string;
|
|
9
|
+
constructor({ eventLockTableName, eventLockTtl, logger, postgresqlObjectStorageMaxRetries, viewLockTableName, viewLockTtl, viewModelPostgresqlDb, viewModelPostgresqlDbFactory }: PostgresqlObjectProjectionParams);
|
|
10
|
+
project(event: IEvent, meta?: Record<string, any>): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IContainer } from 'node-cqrs';
|
|
2
|
+
import type { IEvent, IEventLocker, ILogger, IViewLocker } from '../interfaces/index.ts';
|
|
3
|
+
import { AbstractPostgresqlAccessor } from './AbstractPostgresqlAccessor.ts';
|
|
4
|
+
import { PostgresqlEventLocker, type PostgresqlEventLockerParams } from './PostgresqlEventLocker.ts';
|
|
5
|
+
import { PostgresqlViewLocker, type PostgresqlViewLockerParams } from './PostgresqlViewLocker.ts';
|
|
6
|
+
/**
|
|
7
|
+
* Base class for PostgreSQL-backed projection views with restore locking and last-processed-event tracking.
|
|
8
|
+
*/
|
|
9
|
+
export declare abstract class AbstractPostgresqlView extends AbstractPostgresqlAccessor implements IViewLocker, IEventLocker {
|
|
10
|
+
protected readonly schemaVersion: string;
|
|
11
|
+
protected readonly viewLocker: PostgresqlViewLocker;
|
|
12
|
+
protected readonly eventLocker: PostgresqlEventLocker;
|
|
13
|
+
protected logger: ILogger | undefined;
|
|
14
|
+
get ready(): boolean;
|
|
15
|
+
constructor(options: Partial<Pick<IContainer, 'viewModelPostgresqlDb' | 'viewModelPostgresqlDbFactory' | 'logger'>> & PostgresqlEventLockerParams & PostgresqlViewLockerParams);
|
|
16
|
+
lock(): Promise<boolean>;
|
|
17
|
+
unlock(): Promise<void>;
|
|
18
|
+
once(event: 'ready'): Promise<void>;
|
|
19
|
+
getLastEvent(): Promise<IEvent | undefined>;
|
|
20
|
+
tryMarkAsProjecting(event: IEvent): Promise<boolean>;
|
|
21
|
+
markAsProjected(event: IEvent): Promise<void>;
|
|
22
|
+
markAsLastEvent(event: IEvent): Promise<void>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PostgresqlConnection } from './PostgresqlConnection.ts';
|
|
2
|
+
declare module 'node-cqrs' {
|
|
3
|
+
interface IContainer {
|
|
4
|
+
viewModelPostgresqlDb?: PostgresqlConnection;
|
|
5
|
+
viewModelPostgresqlDbFactory?: () => Promise<PostgresqlConnection> | PostgresqlConnection;
|
|
6
|
+
postgresqlEventStorageConfig?: {
|
|
7
|
+
eventsTableName?: string;
|
|
8
|
+
eventSagasTableName?: string;
|
|
9
|
+
};
|
|
10
|
+
postgresqlObjectStorageMaxRetries?: number;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type PostgresqlQueryResult<TRow extends Record<string, unknown> = Record<string, unknown>> = {
|
|
2
|
+
rows: TRow[];
|
|
3
|
+
rowCount: number | null;
|
|
4
|
+
};
|
|
5
|
+
export type PostgresqlConnection = {
|
|
6
|
+
query<TRow extends Record<string, unknown> = Record<string, unknown>>(text: string, values?: readonly unknown[]): Promise<PostgresqlQueryResult<TRow>>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { IContainer } from 'node-cqrs';
|
|
2
|
+
import type { IEvent, IEventLocker } from '../interfaces/index.ts';
|
|
3
|
+
import type { PostgresqlConnection } from './PostgresqlConnection.ts';
|
|
4
|
+
import type { PostgresqlProjectionDataParams } from './PostgresqlProjectionDataParams.ts';
|
|
5
|
+
import type { PostgresqlViewLockerParams } from './PostgresqlViewLocker.ts';
|
|
6
|
+
import { AbstractPostgresqlAccessor } from './AbstractPostgresqlAccessor.ts';
|
|
7
|
+
export type PostgresqlEventLockerParams = PostgresqlProjectionDataParams & Pick<PostgresqlViewLockerParams, 'viewLockTableName'> & {
|
|
8
|
+
/**
|
|
9
|
+
* (Optional) PostgreSQL table name where event locks are stored.
|
|
10
|
+
*
|
|
11
|
+
* @default PostgresqlEventLocker.DEFAULT_EVENT_LOCK_TABLE
|
|
12
|
+
*/
|
|
13
|
+
eventLockTableName?: string;
|
|
14
|
+
/**
|
|
15
|
+
* (Optional) Time-to-live (TTL) duration in milliseconds
|
|
16
|
+
* for which an event remains in the "processing" state until released.
|
|
17
|
+
*
|
|
18
|
+
* @default PostgresqlEventLocker.DEFAULT_EVENT_LOCK_TTL
|
|
19
|
+
*/
|
|
20
|
+
eventLockTtl?: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* PostgreSQL-backed implementation of IEventLocker.
|
|
24
|
+
*
|
|
25
|
+
* Uses one table for per-event processing locks and the view lock table for the
|
|
26
|
+
* last processed event checkpoint.
|
|
27
|
+
*/
|
|
28
|
+
export declare class PostgresqlEventLocker extends AbstractPostgresqlAccessor implements IEventLocker {
|
|
29
|
+
#private;
|
|
30
|
+
static DEFAULT_EVENT_LOCK_TTL: number;
|
|
31
|
+
static DEFAULT_EVENT_LOCK_TABLE: string;
|
|
32
|
+
static DEFAULT_VIEW_LOCK_TABLE: string;
|
|
33
|
+
constructor(o: Partial<Pick<IContainer, 'viewModelPostgresqlDb' | 'viewModelPostgresqlDbFactory'>> & PostgresqlEventLockerParams);
|
|
34
|
+
protected initialize(db: PostgresqlConnection): Promise<void>;
|
|
35
|
+
tryMarkAsProjecting(event: IEvent): Promise<boolean>;
|
|
36
|
+
markAsProjected(event: IEvent): Promise<void>;
|
|
37
|
+
markAsLastEvent(event: IEvent): Promise<void>;
|
|
38
|
+
getLastEvent(): Promise<IEvent | undefined>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IContainer } from 'node-cqrs';
|
|
2
|
+
import type { AggregateEventsQueryParams, DispatchPipelineBatch, EventQueryAfter, IDispatchPipelineProcessor, IEvent, IEventSet, IEventStorageReader, IEventStream, IIdentifierProvider, Identifier } from '../interfaces/index.ts';
|
|
3
|
+
import { AbstractPostgresqlAccessor } from './AbstractPostgresqlAccessor.ts';
|
|
4
|
+
import type { PostgresqlConnection } from './PostgresqlConnection.ts';
|
|
5
|
+
export declare class PostgresqlEventStorage extends AbstractPostgresqlAccessor implements IEventStorageReader, IIdentifierProvider, IDispatchPipelineProcessor {
|
|
6
|
+
#private;
|
|
7
|
+
static readonly EVENTS_TABLE = "tbl_events";
|
|
8
|
+
static readonly EVENT_SAGAS_TABLE = "tbl_event_sagas";
|
|
9
|
+
constructor({ postgresqlEventStorageConfig, viewModelPostgresqlDb, viewModelPostgresqlDbFactory }: Partial<Pick<IContainer, 'postgresqlEventStorageConfig' | 'viewModelPostgresqlDb' | 'viewModelPostgresqlDbFactory'>>);
|
|
10
|
+
protected initialize(db: PostgresqlConnection): Promise<void>;
|
|
11
|
+
getNewId(): string;
|
|
12
|
+
commitEvents(events: IEventSet, options?: {
|
|
13
|
+
ignoreConcurrencyError?: boolean;
|
|
14
|
+
meta?: Record<string, unknown> | null;
|
|
15
|
+
}): Promise<IEventSet>;
|
|
16
|
+
getAggregateEvents(aggregateId: Identifier, options?: AggregateEventsQueryParams): IEventStream;
|
|
17
|
+
getSagaEvents(sagaId: Identifier, { beforeEvent }: {
|
|
18
|
+
beforeEvent: IEvent;
|
|
19
|
+
}): IEventStream;
|
|
20
|
+
getEventsByTypes(eventTypes: Readonly<string[]>, options?: EventQueryAfter): IEventStream;
|
|
21
|
+
process(batch: DispatchPipelineBatch): Promise<DispatchPipelineBatch>;
|
|
22
|
+
private isAggregateVersionUniqueViolation;
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IContainer } from 'node-cqrs';
|
|
2
|
+
import type { Identifier, IObjectStorage } from '../interfaces/index.ts';
|
|
3
|
+
import { AbstractPostgresqlAccessor } from './AbstractPostgresqlAccessor.ts';
|
|
4
|
+
import type { PostgresqlConnection } from './PostgresqlConnection.ts';
|
|
5
|
+
/**
|
|
6
|
+
* PostgreSQL-backed implementation of IObjectStorage.
|
|
7
|
+
*
|
|
8
|
+
* Each record is stored as a row `{ id, data, version }`.
|
|
9
|
+
* The version field enables optimistic concurrency control: `update` and
|
|
10
|
+
* `updateEnforcingNew` re-read the record after the user callback runs and
|
|
11
|
+
* atomically commit only when the version still matches.
|
|
12
|
+
* On mismatch the operation retries up to `maxRetries` times.
|
|
13
|
+
*/
|
|
14
|
+
export declare class PostgresqlObjectStorage<TRecord> extends AbstractPostgresqlAccessor implements IObjectStorage<TRecord> {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(o: Partial<Pick<IContainer, 'viewModelPostgresqlDb' | 'viewModelPostgresqlDbFactory'>> & {
|
|
17
|
+
tableName: string;
|
|
18
|
+
maxRetries?: number;
|
|
19
|
+
});
|
|
20
|
+
protected initialize(db: PostgresqlConnection): Promise<void>;
|
|
21
|
+
get(id: Identifier): Promise<TRecord | undefined>;
|
|
22
|
+
create(id: Identifier, data: TRecord): Promise<void>;
|
|
23
|
+
update(id: Identifier, update: (r: TRecord) => TRecord): Promise<void>;
|
|
24
|
+
updateEnforcingNew(id: Identifier, update: (r?: TRecord) => TRecord): Promise<void>;
|
|
25
|
+
delete(id: Identifier): Promise<boolean>;
|
|
26
|
+
private getRow;
|
|
27
|
+
private updateByVersion;
|
|
28
|
+
private static parseData;
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IEventLocker, Identifier, IObjectStorage } from '../interfaces/index.ts';
|
|
2
|
+
import { AbstractPostgresqlView } from './AbstractPostgresqlView.ts';
|
|
3
|
+
import type { PostgresqlConnection } from './PostgresqlConnection.ts';
|
|
4
|
+
/**
|
|
5
|
+
* PostgreSQL-backed object view with restore locking and last-processed-event tracking.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PostgresqlObjectView<TRecord> extends AbstractPostgresqlView implements IObjectStorage<TRecord>, IEventLocker {
|
|
8
|
+
#private;
|
|
9
|
+
constructor(options: ConstructorParameters<typeof AbstractPostgresqlView>[0] & {
|
|
10
|
+
tableNamePrefix: string;
|
|
11
|
+
postgresqlObjectStorageMaxRetries?: number;
|
|
12
|
+
});
|
|
13
|
+
protected initialize(_db: PostgresqlConnection): Promise<void> | void;
|
|
14
|
+
get(id: Identifier): Promise<TRecord | undefined>;
|
|
15
|
+
create(id: Identifier, data: TRecord): Promise<void>;
|
|
16
|
+
update(id: Identifier, update: (r: TRecord) => TRecord): Promise<void>;
|
|
17
|
+
updateEnforcingNew(id: Identifier, update: (r?: TRecord) => TRecord): Promise<void>;
|
|
18
|
+
delete(id: Identifier): Promise<boolean>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type PostgresqlProjectionDataParams = {
|
|
2
|
+
/**
|
|
3
|
+
* Unique identifier for the projection, used with the schema version to distinguish data ownership.
|
|
4
|
+
*/
|
|
5
|
+
projectionName: string;
|
|
6
|
+
/**
|
|
7
|
+
* The version of the schema used for data produced by the projection.
|
|
8
|
+
* When the projection's output format changes, this version should be incremented.
|
|
9
|
+
* A version change indicates that previously stored data is obsolete and must be rebuilt.
|
|
10
|
+
*
|
|
11
|
+
* @example "20250519", "1.0.0"
|
|
12
|
+
*/
|
|
13
|
+
schemaVersion: string;
|
|
14
|
+
};
|