pipework 0.98.0 → 0.99.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 +4 -0
- package/dist/data/write/audit/bound-transaction.d.ts +4 -0
- package/dist/data/write/audit/bound-transaction.d.ts.map +1 -0
- package/dist/data/write/audit/bound-transaction.js +12 -0
- package/dist/data/write/audit/bound-transaction.js.map +1 -0
- package/dist/infra/alerts/audit.d.ts +5 -1
- package/dist/infra/alerts/audit.d.ts.map +1 -1
- package/dist/infra/alerts/audit.js +19 -7
- package/dist/infra/alerts/audit.js.map +1 -1
- package/dist/infra/email/audit.d.ts +5 -1
- package/dist/infra/email/audit.d.ts.map +1 -1
- package/dist/infra/email/audit.js +19 -7
- package/dist/infra/email/audit.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.99.0
|
|
4
|
+
|
|
5
|
+
- **Fixed: an alert or email audit record no longer waits on the Flow's own transaction** (#562). `auditAlert` (raise, resolve, acknowledge, link, digest, delivery) and the email audit wrote on a second connection; with `audit: { chain: true }`, after an audited write in the same transaction that insert waited on the tenant's chain-head lock, held until the transaction committed, while the transaction waited on the call: a hang Postgres cannot see. When the Flow has a transaction open on that database, the record is now written in it (a savepoint, so a failed write cannot abort it) and commits or rolls back with it; the alert row itself is still written on the system channel and kept on rollback. With no transaction open, nothing changes.
|
|
6
|
+
|
|
3
7
|
## 0.98.0
|
|
4
8
|
|
|
5
9
|
- **Changed: access links' `resolveRateLimit` counts only codes that match no link, in Postgres across processes** (#560). It used to count every `resolve` and `beginUse`, hits included, in each process's memory, so 30 per minute served about 5 people opening one link from one address, and N processes gave a guesser N times the budget. Now only a code that matches no link counts (revoked, expired and exhausted links do not), in the new `access_link_miss` table, one fixed window per client shared by every process. Default 30 misses per 60 s; a client over it gets `rate-limited` for every code, valid ones included. `max` and `windowMs` are now checked at `accessLinks.create`. **Migration:** the new `access_link_miss` table; regenerate migrations.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bound-transaction.d.ts","sourceRoot":"","sources":["../../../../src/data/write/audit/bound-transaction.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAA;AAC3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAA;AAG7D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,EAAE,GAAG,IAAI,CAMzE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The Flow's open transaction on a database, for audit writers that must not
|
|
2
|
+
// wait on it from a second connection. Null when none is bound.
|
|
3
|
+
import { verifyTransactionBinding } from '../../../request/context/guard.js';
|
|
4
|
+
export function boundTransaction(flow, database) {
|
|
5
|
+
const name = database ?? flow.instance.config.defaultDatabaseName();
|
|
6
|
+
const bound = flow.transactions.get(name);
|
|
7
|
+
if (bound === undefined)
|
|
8
|
+
return null;
|
|
9
|
+
verifyTransactionBinding(flow, bound, name);
|
|
10
|
+
return bound.drizzle;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=bound-transaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bound-transaction.js","sourceRoot":"","sources":["../../../../src/data/write/audit/bound-transaction.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,gEAAgE;AAIhE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAA;AAE5E,MAAM,UAAU,gBAAgB,CAAC,IAAU,EAAE,QAAiB;IAC5D,MAAM,IAAI,GAAG,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,mBAAmB,EAAE,CAAA;IACnE,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACzC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACpC,wBAAwB,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;IAC3C,OAAO,KAAK,CAAC,OAAO,CAAA;AACtB,CAAC"}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Writes an `Alert` audit record under the Flow's open trace step; does nothing when no step is open.
|
|
3
|
+
* In the Flow's transaction on that database when one is open (a savepoint, so a failed write cannot abort it; it
|
|
4
|
+
* commits or rolls back with it), else on the system channel.
|
|
5
|
+
*/
|
|
2
6
|
export declare function auditAlert(verb: string, key: string, details: Record<string, unknown>, database?: string): Promise<void>;
|
|
3
7
|
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/infra/alerts/audit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/infra/alerts/audit.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB9H"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { sql } from '../../data/db/sql.js';
|
|
2
2
|
import { pipeSystem } from '../../data/db/db.js';
|
|
3
|
+
import { boundTransaction } from '../../data/write/audit/bound-transaction.js';
|
|
3
4
|
import { getContext } from '../../request/context/store.js';
|
|
4
5
|
import { resolveActorId } from '../../data/write/audit/actor.js';
|
|
5
6
|
import { getBaseLogger } from '../../core/logging/index.js';
|
|
6
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Writes an `Alert` audit record under the Flow's open trace step; does nothing when no step is open.
|
|
9
|
+
* In the Flow's transaction on that database when one is open (a savepoint, so a failed write cannot abort it; it
|
|
10
|
+
* commits or rolls back with it), else on the system channel.
|
|
11
|
+
*/
|
|
7
12
|
export async function auditAlert(verb, key, details, database) {
|
|
8
13
|
const flow = getContext();
|
|
9
14
|
const trace = flow?.trace;
|
|
@@ -11,12 +16,19 @@ export async function auditAlert(verb, key, details, database) {
|
|
|
11
16
|
if (flow === undefined || trace === undefined || trace === null || stepId === null)
|
|
12
17
|
return;
|
|
13
18
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const write = async (db) => {
|
|
20
|
+
await db.execute(sql `
|
|
21
|
+
INSERT INTO audit_record
|
|
22
|
+
(trace_day, trace_id, trace_step_id, tenant_id, actor_id, entity, verb, entity_id, after, occurred_at)
|
|
23
|
+
VALUES (${trace.openedDay}, ${trace.id}, ${stepId}, ${flow.tenant}, ${resolveActorId(flow)},
|
|
24
|
+
'Alert', ${verb}, ${key}, ${JSON.stringify(details)}::jsonb, now())
|
|
25
|
+
`);
|
|
26
|
+
};
|
|
27
|
+
const tx = boundTransaction(flow, database);
|
|
28
|
+
if (tx === null)
|
|
29
|
+
await write(pipeSystem(database));
|
|
30
|
+
else
|
|
31
|
+
await tx.transaction(write);
|
|
20
32
|
}
|
|
21
33
|
catch (err) {
|
|
22
34
|
getBaseLogger().error({ err, traceId: trace.id }, `Failed to write the alert ${verb} audit record`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/infra/alerts/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/infra/alerts/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAA;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAE3D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,GAAW,EAAE,OAAgC,EAAE,QAAiB;IAC7G,MAAM,IAAI,GAAG,UAAU,EAAE,CAAA;IACzB,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,CAAA;IACzB,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI,CAAA;IAC/C,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;QAAE,OAAM;IAC1F,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,EAAE,EAAM,EAAiB,EAAE;YAC5C,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;;;kBAGR,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,EAAE,KAAK,MAAM,KAAK,IAAI,CAAC,MAAM,KAAK,cAAc,CAAC,IAAI,CAAC;qBAC7E,IAAI,KAAK,GAAG,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;OACtD,CAAC,CAAA;QACJ,CAAC,CAAA;QACD,MAAM,EAAE,GAAG,gBAAgB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAC3C,IAAI,EAAE,KAAK,IAAI;YAAE,MAAM,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAA;;YAC7C,MAAM,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,aAAa,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,6BAA6B,IAAI,eAAe,CAAC,CAAA;IACrG,CAAC;AACH,CAAC"}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Writes an audit record (entity `Email` unless given) under the Flow's open trace step; does nothing when no step is open.
|
|
3
|
+
* In the Flow's transaction on that database when one is open (a savepoint, so a failed write cannot abort it; it
|
|
4
|
+
* commits or rolls back with it), else on the system channel.
|
|
5
|
+
*/
|
|
2
6
|
export declare function auditEmail(verb: string, entityId: string | null, details: Record<string, unknown>, entity?: string): Promise<void>;
|
|
3
7
|
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/infra/email/audit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/infra/email/audit.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBzI"}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { sql } from '../../data/db/sql.js';
|
|
2
2
|
import { pipeSystem } from '../../data/db/db.js';
|
|
3
|
+
import { boundTransaction } from '../../data/write/audit/bound-transaction.js';
|
|
3
4
|
import { getContext } from '../../request/context/store.js';
|
|
4
5
|
import { resolveActorId } from '../../data/write/audit/actor.js';
|
|
5
6
|
import { getBaseLogger } from '../../core/logging/index.js';
|
|
6
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Writes an audit record (entity `Email` unless given) under the Flow's open trace step; does nothing when no step is open.
|
|
9
|
+
* In the Flow's transaction on that database when one is open (a savepoint, so a failed write cannot abort it; it
|
|
10
|
+
* commits or rolls back with it), else on the system channel.
|
|
11
|
+
*/
|
|
7
12
|
export async function auditEmail(verb, entityId, details, entity = 'Email') {
|
|
8
13
|
const flow = getContext();
|
|
9
14
|
const trace = flow?.trace;
|
|
@@ -11,12 +16,19 @@ export async function auditEmail(verb, entityId, details, entity = 'Email') {
|
|
|
11
16
|
if (flow === undefined || trace === undefined || trace === null || stepId === null)
|
|
12
17
|
return;
|
|
13
18
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
${
|
|
19
|
-
|
|
19
|
+
const write = async (db) => {
|
|
20
|
+
await db.execute(sql `
|
|
21
|
+
INSERT INTO audit_record
|
|
22
|
+
(trace_day, trace_id, trace_step_id, tenant_id, actor_id, entity, verb, entity_id, after, occurred_at)
|
|
23
|
+
VALUES (${trace.openedDay}, ${trace.id}, ${stepId}, ${flow.tenant}, ${resolveActorId(flow)},
|
|
24
|
+
${entity}, ${verb}, ${entityId}, ${JSON.stringify(details)}::jsonb, now())
|
|
25
|
+
`);
|
|
26
|
+
};
|
|
27
|
+
const tx = boundTransaction(flow);
|
|
28
|
+
if (tx === null)
|
|
29
|
+
await write(pipeSystem());
|
|
30
|
+
else
|
|
31
|
+
await tx.transaction(write);
|
|
20
32
|
}
|
|
21
33
|
catch (err) {
|
|
22
34
|
getBaseLogger().error({ err, traceId: trace.id }, `Failed to write the email ${verb} audit record`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/infra/email/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/infra/email/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAA;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAA;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAE3D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY,EAAE,QAAuB,EAAE,OAAgC,EAAE,MAAM,GAAG,OAAO;IACxH,MAAM,IAAI,GAAG,UAAU,EAAE,CAAA;IACzB,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,CAAA;IACzB,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI,CAAA;IAC/C,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;QAAE,OAAM;IAC1F,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,EAAE,EAAM,EAAiB,EAAE;YAC5C,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA;;;kBAGR,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,EAAE,KAAK,MAAM,KAAK,IAAI,CAAC,MAAM,KAAK,cAAc,CAAC,IAAI,CAAC;YACtF,MAAM,KAAK,IAAI,KAAK,QAAQ,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;OAC7D,CAAC,CAAA;QACJ,CAAC,CAAA;QACD,MAAM,EAAE,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,EAAE,KAAK,IAAI;YAAE,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC,CAAA;;YACrC,MAAM,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,aAAa,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,6BAA6B,IAAI,eAAe,CAAC,CAAA;IACrG,CAAC;AACH,CAAC"}
|