@rotorsoft/act-pg 1.12.0 → 1.13.1
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/dist/.tsbuildinfo +1 -1
- package/dist/@types/postgres-store.d.ts +8 -1
- package/dist/@types/postgres-store.d.ts.map +1 -1
- package/dist/index.cjs +96 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +96 -51
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -413,16 +413,13 @@ var PostgresStore = class {
|
|
|
413
413
|
async commit(stream, msgs, meta, expectedVersion) {
|
|
414
414
|
if (msgs.length === 0) return [];
|
|
415
415
|
const client = await this._client("commit");
|
|
416
|
-
let version = -1;
|
|
417
416
|
try {
|
|
418
|
-
await client.query("BEGIN");
|
|
419
417
|
const last = await client.query(
|
|
420
|
-
`SELECT version
|
|
421
|
-
|
|
422
|
-
WHERE stream=$1 ORDER BY version DESC LIMIT 1`,
|
|
418
|
+
`SELECT version FROM ${this._fqt}
|
|
419
|
+
WHERE stream=$1 ORDER BY version DESC LIMIT 1`,
|
|
423
420
|
[stream]
|
|
424
421
|
);
|
|
425
|
-
version = last.
|
|
422
|
+
let version = last.rows.at(0)?.version ?? -1;
|
|
426
423
|
if (typeof expectedVersion === "number" && version !== expectedVersion)
|
|
427
424
|
throw new ConcurrencyError(
|
|
428
425
|
stream,
|
|
@@ -430,52 +427,69 @@ var PostgresStore = class {
|
|
|
430
427
|
msgs,
|
|
431
428
|
expectedVersion
|
|
432
429
|
);
|
|
433
|
-
const
|
|
430
|
+
const base_version = version;
|
|
431
|
+
const names = [];
|
|
432
|
+
const datas = [];
|
|
433
|
+
const piis = [];
|
|
434
|
+
const versions = [];
|
|
434
435
|
for (const { name, data, pii } of msgs) {
|
|
435
436
|
version++;
|
|
436
|
-
|
|
437
|
+
names.push(name);
|
|
438
|
+
datas.push(JSON.stringify(data));
|
|
439
|
+
piis.push(
|
|
440
|
+
this._resolve_pii_key && pii != null ? JSON.stringify(await encrypt(pii, this._resolve_pii_key)) : pii != null ? JSON.stringify(pii) : null
|
|
441
|
+
);
|
|
442
|
+
versions.push(version);
|
|
443
|
+
}
|
|
444
|
+
const insert_select = msgs.length === 1 ? `SELECT $1, $2::jsonb, $3::jsonb, $5, $4::int, $6 FROM l` : `SELECT u.name, u.data::jsonb, u.pii::jsonb, $5, u.version, $6
|
|
445
|
+
FROM l, unnest($1::text[], $2::text[], $3::text[], $4::int[])
|
|
446
|
+
WITH ORDINALITY AS u(name, data, pii, version, ord)
|
|
447
|
+
ORDER BY u.ord`;
|
|
448
|
+
const notify_ctes = this.config.notify ? `,
|
|
449
|
+
payload AS (
|
|
450
|
+
SELECT json_build_object(
|
|
451
|
+
'stream', $5::text,
|
|
452
|
+
'events', json_agg(json_build_object('id', ins.id, 'name', ins.name) ORDER BY ins.version),
|
|
453
|
+
'by', $9::text
|
|
454
|
+
)::text AS p
|
|
455
|
+
FROM ins
|
|
456
|
+
),
|
|
457
|
+
n AS (
|
|
458
|
+
SELECT pg_notify($8, payload.p) FROM payload
|
|
459
|
+
WHERE octet_length(payload.p) < $10
|
|
460
|
+
)` : "";
|
|
461
|
+
const final_select = this.config.notify ? "SELECT ins.* FROM ins LEFT JOIN n ON true ORDER BY ins.version" : "SELECT * FROM ins ORDER BY version";
|
|
462
|
+
const sql = `WITH l AS (SELECT pg_advisory_xact_lock(hashtext($7))),
|
|
463
|
+
ins AS (
|
|
437
464
|
INSERT INTO ${this._fqt}(name, data, pii, stream, version, meta)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
stream,
|
|
453
|
-
version - 1,
|
|
454
|
-
msgs,
|
|
455
|
-
expectedVersion ?? -1
|
|
456
|
-
);
|
|
465
|
+
${insert_select}
|
|
466
|
+
RETURNING *
|
|
467
|
+
)${notify_ctes}
|
|
468
|
+
${final_select}`;
|
|
469
|
+
const base_params = msgs.length === 1 ? [names[0], datas[0], piis[0], versions[0], stream, meta, this._fqt] : [names, datas, piis, versions, stream, meta, this._fqt];
|
|
470
|
+
const params = this.config.notify ? [...base_params, this._channel, this._by, NOTIFY_MAX_PAYLOAD_BYTES] : base_params;
|
|
471
|
+
try {
|
|
472
|
+
const { rows } = await client.query(sql, params);
|
|
473
|
+
if (this._resolve_pii_key) {
|
|
474
|
+
for (const row of rows) {
|
|
475
|
+
if (typeof row.pii === "string") {
|
|
476
|
+
const decrypted = await decrypt(row.pii, this._resolve_pii_key);
|
|
477
|
+
row.pii = decrypted;
|
|
478
|
+
}
|
|
457
479
|
}
|
|
458
|
-
throw error;
|
|
459
480
|
}
|
|
481
|
+
return rows;
|
|
482
|
+
} catch (error) {
|
|
483
|
+
if (error?.code === PG_UNIQUE_VIOLATION) {
|
|
484
|
+
throw new ConcurrencyError(
|
|
485
|
+
stream,
|
|
486
|
+
base_version,
|
|
487
|
+
msgs,
|
|
488
|
+
expectedVersion ?? -1
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
throw error;
|
|
460
492
|
}
|
|
461
|
-
if (this.config.notify) {
|
|
462
|
-
const payload = JSON.stringify({
|
|
463
|
-
stream,
|
|
464
|
-
events: committed.map((c) => ({ id: c.id, name: c.name })),
|
|
465
|
-
by: this._by
|
|
466
|
-
});
|
|
467
|
-
if (Buffer.byteLength(payload, "utf8") < NOTIFY_MAX_PAYLOAD_BYTES)
|
|
468
|
-
await client.query(`SELECT pg_notify($1, $2)`, [
|
|
469
|
-
this._channel,
|
|
470
|
-
payload
|
|
471
|
-
]);
|
|
472
|
-
}
|
|
473
|
-
await client.query("COMMIT");
|
|
474
|
-
return committed;
|
|
475
|
-
} catch (error) {
|
|
476
|
-
await client.query("ROLLBACK").catch(() => {
|
|
477
|
-
});
|
|
478
|
-
throw error;
|
|
479
493
|
} finally {
|
|
480
494
|
client.release();
|
|
481
495
|
}
|
|
@@ -1200,20 +1214,30 @@ var PostgresStore = class {
|
|
|
1200
1214
|
}
|
|
1201
1215
|
/**
|
|
1202
1216
|
* Atomically truncates streams and seeds each with a snapshot or tombstone.
|
|
1203
|
-
*
|
|
1217
|
+
* Windowed targets (`before` set) prune the prefix below the closest safe
|
|
1218
|
+
* `__snapshot__` instead — no seed, subscriptions untouched, no-op when no
|
|
1219
|
+
* snapshot qualifies.
|
|
1220
|
+
* @param targets - Streams to truncate with optional snapshot state and meta,
|
|
1221
|
+
* or a `before`/`max_id` boundary for a windowed prefix delete.
|
|
1204
1222
|
* @returns Map keyed by stream name, each entry with `deleted` count and `committed` event.
|
|
1205
1223
|
*/
|
|
1206
1224
|
async truncate(targets) {
|
|
1207
1225
|
if (!targets.length) return /* @__PURE__ */ new Map();
|
|
1208
|
-
const
|
|
1226
|
+
const full = targets.filter((t) => t.before === void 0);
|
|
1227
|
+
const windowed = targets.filter((t) => t.before !== void 0);
|
|
1209
1228
|
const client = await this._client("truncate");
|
|
1210
1229
|
try {
|
|
1211
1230
|
await client.query("BEGIN");
|
|
1212
|
-
await client.query(
|
|
1213
|
-
|
|
1231
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtext($1))", [
|
|
1232
|
+
this._fqt
|
|
1214
1233
|
]);
|
|
1215
1234
|
const result = /* @__PURE__ */ new Map();
|
|
1216
|
-
|
|
1235
|
+
if (full.length) {
|
|
1236
|
+
await client.query(`DELETE FROM ${this._fqs} WHERE stream = ANY($1)`, [
|
|
1237
|
+
full.map((t) => t.stream)
|
|
1238
|
+
]);
|
|
1239
|
+
}
|
|
1240
|
+
for (const { stream, snapshot, meta } of full) {
|
|
1217
1241
|
const { rowCount } = await client.query(
|
|
1218
1242
|
`DELETE FROM ${this._fqt} WHERE stream = $1`,
|
|
1219
1243
|
[stream]
|
|
@@ -1234,6 +1258,27 @@ var PostgresStore = class {
|
|
|
1234
1258
|
committed: rows[0]
|
|
1235
1259
|
});
|
|
1236
1260
|
}
|
|
1261
|
+
for (const { stream, before, max_id } of windowed) {
|
|
1262
|
+
const { rows } = await client.query(
|
|
1263
|
+
`SELECT id, stream, version, name, data, created, meta
|
|
1264
|
+
FROM ${this._fqt}
|
|
1265
|
+
WHERE stream = $1 AND name = $2 AND created < $3
|
|
1266
|
+
AND ($4::int IS NULL OR id <= $4)
|
|
1267
|
+
ORDER BY id DESC LIMIT 1`,
|
|
1268
|
+
[stream, SNAP_EVENT, before, max_id ?? null]
|
|
1269
|
+
);
|
|
1270
|
+
if (!rows.length) continue;
|
|
1271
|
+
const boundary = rows[0];
|
|
1272
|
+
const { rowCount } = await client.query(
|
|
1273
|
+
`DELETE FROM ${this._fqt} WHERE stream = $1 AND id < $2`,
|
|
1274
|
+
[stream, boundary.id]
|
|
1275
|
+
);
|
|
1276
|
+
result.set(stream, {
|
|
1277
|
+
deleted: rowCount ?? 0,
|
|
1278
|
+
committed: boundary,
|
|
1279
|
+
before
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1237
1282
|
await client.query("COMMIT");
|
|
1238
1283
|
return result;
|
|
1239
1284
|
} catch (error) {
|