pipework 0.67.0 → 0.69.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/REFERENCE.md +112 -0
- package/dist/core/config/namespace.d.ts +2 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/schema.d.ts +6 -0
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +4 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/data/migrate/internal-definitions.d.ts.map +1 -1
- package/dist/data/migrate/internal-definitions.js +10 -0
- package/dist/data/migrate/internal-definitions.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/infra/alerts/audit.d.ts +3 -0
- package/dist/infra/alerts/audit.d.ts.map +1 -0
- package/dist/infra/alerts/audit.js +25 -0
- package/dist/infra/alerts/audit.js.map +1 -0
- package/dist/infra/alerts/handle.d.ts +95 -0
- package/dist/infra/alerts/handle.d.ts.map +1 -0
- package/dist/infra/alerts/handle.js +301 -0
- package/dist/infra/alerts/handle.js.map +1 -0
- package/dist/infra/alerts/index.d.ts +5 -0
- package/dist/infra/alerts/index.d.ts.map +1 -0
- package/dist/infra/alerts/index.js +2 -0
- package/dist/infra/alerts/index.js.map +1 -0
- package/dist/infra/alerts/namespace.d.ts +23 -0
- package/dist/infra/alerts/namespace.d.ts.map +1 -0
- package/dist/infra/alerts/namespace.js +23 -0
- package/dist/infra/alerts/namespace.js.map +1 -0
- package/dist/infra/alerts/schema.d.ts +123 -0
- package/dist/infra/alerts/schema.d.ts.map +1 -0
- package/dist/infra/alerts/schema.js +55 -0
- package/dist/infra/alerts/schema.js.map +1 -0
- package/dist/infra/alerts/sinks.d.ts +60 -0
- package/dist/infra/alerts/sinks.d.ts.map +1 -0
- package/dist/infra/alerts/sinks.js +50 -0
- package/dist/infra/alerts/sinks.js.map +1 -0
- package/dist/infra/alerts/sources.d.ts +55 -0
- package/dist/infra/alerts/sources.d.ts.map +1 -0
- package/dist/infra/alerts/sources.js +134 -0
- package/dist/infra/alerts/sources.js.map +1 -0
- package/dist/infra/aws/sigv4.d.ts +46 -0
- package/dist/infra/aws/sigv4.d.ts.map +1 -0
- package/dist/infra/aws/sigv4.js +94 -0
- package/dist/infra/aws/sigv4.js.map +1 -0
- package/dist/infra/email/index.d.ts +1 -1
- package/dist/infra/email/index.d.ts.map +1 -1
- package/dist/infra/email/index.js +1 -1
- package/dist/infra/email/index.js.map +1 -1
- package/dist/infra/email/providers.d.ts +1 -1
- package/dist/infra/email/providers.d.ts.map +1 -1
- package/dist/infra/email/providers.js +1 -1
- package/dist/infra/email/providers.js.map +1 -1
- package/dist/infra/email/sns.d.ts +1 -1
- package/dist/infra/email/sns.js +1 -1
- package/dist/infra/storage/backend.d.ts +32 -0
- package/dist/infra/storage/backend.d.ts.map +1 -0
- package/dist/infra/storage/backend.js +2 -0
- package/dist/infra/storage/backend.js.map +1 -0
- package/dist/infra/storage/handle.d.ts +110 -0
- package/dist/infra/storage/handle.d.ts.map +1 -0
- package/dist/infra/storage/handle.js +317 -0
- package/dist/infra/storage/handle.js.map +1 -0
- package/dist/infra/storage/index.d.ts +8 -0
- package/dist/infra/storage/index.d.ts.map +1 -0
- package/dist/infra/storage/index.js +4 -0
- package/dist/infra/storage/index.js.map +1 -0
- package/dist/infra/storage/local.d.ts +16 -0
- package/dist/infra/storage/local.d.ts.map +1 -0
- package/dist/infra/storage/local.js +95 -0
- package/dist/infra/storage/local.js.map +1 -0
- package/dist/infra/storage/namespace.d.ts +13 -0
- package/dist/infra/storage/namespace.d.ts.map +1 -0
- package/dist/infra/storage/namespace.js +13 -0
- package/dist/infra/storage/namespace.js.map +1 -0
- package/dist/infra/storage/s3.d.ts +34 -0
- package/dist/infra/storage/s3.d.ts.map +1 -0
- package/dist/infra/storage/s3.js +220 -0
- package/dist/infra/storage/s3.js.map +1 -0
- package/dist/infra/storage/schema.d.ts +43 -0
- package/dist/infra/storage/schema.d.ts.map +1 -0
- package/dist/infra/storage/schema.js +23 -0
- package/dist/infra/storage/schema.js.map +1 -0
- package/dist/infra/webhook/sign.d.ts +2 -0
- package/dist/infra/webhook/sign.d.ts.map +1 -1
- package/dist/infra/webhook/sign.js +1 -1
- package/dist/infra/webhook/sign.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { WebhookSignatureOptions } from '../webhook/sign.js';
|
|
2
|
+
/** What a sink receives, and what the webhook sink POSTs as its body. */
|
|
3
|
+
export interface AlertPayload {
|
|
4
|
+
readonly kind: 'raise' | 'repeat' | 'resolve';
|
|
5
|
+
readonly key: string;
|
|
6
|
+
readonly severity: 'info' | 'warning' | 'error' | 'critical';
|
|
7
|
+
readonly message: string;
|
|
8
|
+
readonly subject: {
|
|
9
|
+
readonly tenantId?: string;
|
|
10
|
+
readonly entity?: string;
|
|
11
|
+
readonly entityId?: string;
|
|
12
|
+
} | null;
|
|
13
|
+
readonly count: number;
|
|
14
|
+
readonly firstSeenAt: string;
|
|
15
|
+
readonly lastSeenAt: string;
|
|
16
|
+
readonly resolvedAt: string | null;
|
|
17
|
+
/** The latest traces that raised it, newest first. */
|
|
18
|
+
readonly traceIds: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface AlertDelivery {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly kind: AlertPayload['kind'];
|
|
23
|
+
readonly key: string;
|
|
24
|
+
readonly sink: string;
|
|
25
|
+
readonly payload: AlertPayload;
|
|
26
|
+
/** 1 on the first try. */
|
|
27
|
+
readonly attempt: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A place alerts are sent. `send` throwing is a failed attempt; so is running past 10 s, which is all the
|
|
31
|
+
* handle gives a send (the row is leased for 30 s). `delivery.id` is the same on every attempt, so a receiver can dedupe on it.
|
|
32
|
+
*/
|
|
33
|
+
export interface AlertSink {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
send(delivery: AlertDelivery): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export interface AlertWebhookConfig {
|
|
38
|
+
/** Sink name; default `'webhook'`. Two sinks on one handle need distinct names. */
|
|
39
|
+
readonly name?: string;
|
|
40
|
+
readonly url: string;
|
|
41
|
+
readonly secret: string;
|
|
42
|
+
/** Request timeout; default 10 000, at most 20 000 (the handle gives a send 10 s and leases the row for 30 s). */
|
|
43
|
+
readonly timeoutMs?: number;
|
|
44
|
+
readonly signing?: WebhookSignatureOptions;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* POSTs the payload as JSON with `X-Webhook-Id`, `X-Webhook-Timestamp` and `X-Webhook-Signature`
|
|
48
|
+
* (`X-Webhook-Id` is the delivery id, the same on every attempt; HMAC over `<id>.<timestamp>.<body>`, as `webhook.sign` makes them). Any non-2xx status fails the attempt.
|
|
49
|
+
*/
|
|
50
|
+
export declare function webhookSink(config: AlertWebhookConfig): AlertSink;
|
|
51
|
+
export interface MemoryAlertSink extends AlertSink {
|
|
52
|
+
/** Every delivery received, in order. */
|
|
53
|
+
readonly delivered: AlertDelivery[];
|
|
54
|
+
clear(): void;
|
|
55
|
+
}
|
|
56
|
+
/** A sink that keeps what it receives, for tests. */
|
|
57
|
+
export declare function memorySink(options?: {
|
|
58
|
+
name?: string;
|
|
59
|
+
}): MemoryAlertSink;
|
|
60
|
+
//# sourceMappingURL=sinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.d.ts","sourceRoot":"","sources":["../../../src/infra/alerts/sinks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAEjE,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAA;IAC7C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAA;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC7G,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAA;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,0BAA0B;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,kBAAkB;IACjC,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,kHAAkH;IAClH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,uBAAuB,CAAA;CAC3C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,CA8BjE;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,CAAA;IACnC,KAAK,IAAI,IAAI,CAAA;CACd;AAED,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,eAAe,CAS3E"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { signPayload } from '../webhook/sign.js';
|
|
2
|
+
/**
|
|
3
|
+
* POSTs the payload as JSON with `X-Webhook-Id`, `X-Webhook-Timestamp` and `X-Webhook-Signature`
|
|
4
|
+
* (`X-Webhook-Id` is the delivery id, the same on every attempt; HMAC over `<id>.<timestamp>.<body>`, as `webhook.sign` makes them). Any non-2xx status fails the attempt.
|
|
5
|
+
*/
|
|
6
|
+
export function webhookSink(config) {
|
|
7
|
+
const timeoutMs = config.timeoutMs ?? 10_000;
|
|
8
|
+
if (timeoutMs > 20_000) {
|
|
9
|
+
throw new Error(`[pipework] alerts.webhook: timeoutMs ${String(timeoutMs)} is over 20000. A delivery is leased for 30 s while it is sent; a longer send could be picked up and sent twice.`);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
name: config.name ?? 'webhook',
|
|
13
|
+
async send(delivery) {
|
|
14
|
+
const signed = signPayload(delivery.payload, config.secret, { ...config.signing, messageId: delivery.id });
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(config.url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
'X-Webhook-Id': signed.messageId,
|
|
23
|
+
'X-Webhook-Timestamp': signed.timestamp.toString(),
|
|
24
|
+
'X-Webhook-Signature': signed.signature,
|
|
25
|
+
},
|
|
26
|
+
body: signed.body,
|
|
27
|
+
signal: controller.signal,
|
|
28
|
+
});
|
|
29
|
+
await response.text().catch(() => '');
|
|
30
|
+
if (response.status < 200 || response.status >= 300)
|
|
31
|
+
throw new Error(`webhook answered HTTP ${String(response.status)}`);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** A sink that keeps what it receives, for tests. */
|
|
40
|
+
export function memorySink(options = {}) {
|
|
41
|
+
const delivered = [];
|
|
42
|
+
return {
|
|
43
|
+
name: options.name ?? 'memory',
|
|
44
|
+
delivered,
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- the sink contract is async
|
|
46
|
+
async send(delivery) { delivered.push(delivery); },
|
|
47
|
+
clear() { delivered.length = 0; },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=sinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.js","sourceRoot":"","sources":["../../../src/infra/alerts/sinks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AA+ChD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAA0B;IACpD,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAA;IAC5C,IAAI,SAAS,GAAG,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,wCAAwC,MAAM,CAAC,SAAS,CAAC,kHAAkH,CAAC,CAAA;IAC9L,CAAC;IACD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,SAAS;QAC9B,KAAK,CAAC,IAAI,CAAC,QAAQ;YACjB,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;YAC1G,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;YACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAA;YAC7D,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE;oBACvC,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;wBAClC,cAAc,EAAE,MAAM,CAAC,SAAS;wBAChC,qBAAqB,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE;wBAClD,qBAAqB,EAAE,MAAM,CAAC,SAAS;qBACxC;oBACD,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,MAAM,EAAE,UAAU,CAAC,MAAM;iBAC1B,CAAC,CAAA;gBACF,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;gBACrC,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG;oBAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAC1H,CAAC;oBAAS,CAAC;gBACT,YAAY,CAAC,KAAK,CAAC,CAAA;YACrB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAQD,qDAAqD;AACrD,MAAM,UAAU,UAAU,CAAC,UAA6B,EAAE;IACxD,MAAM,SAAS,GAAoB,EAAE,CAAA;IACrC,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,QAAQ;QAC9B,SAAS;QACT,0FAA0F;QAC1F,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA,CAAC,CAAC;QACjD,KAAK,KAAK,SAAS,CAAC,MAAM,GAAG,CAAC,CAAA,CAAC,CAAC;KACjC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { HealthCheck } from '../../core/lifecycle/types.js';
|
|
2
|
+
import type { AlertSeverity } from './handle.js';
|
|
3
|
+
/** A condition a source finds true right now. */
|
|
4
|
+
export interface AlertCondition {
|
|
5
|
+
readonly key: string;
|
|
6
|
+
readonly severity: AlertSeverity;
|
|
7
|
+
readonly message: string;
|
|
8
|
+
readonly subject?: {
|
|
9
|
+
tenantId?: string;
|
|
10
|
+
entity?: string;
|
|
11
|
+
entityId?: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Evaluated on every tick. Each returned condition is raised (so an ongoing one groups). When
|
|
16
|
+
* `owns` is given, an open alert the source owns that it no longer returns is resolved.
|
|
17
|
+
*/
|
|
18
|
+
export interface AlertSource {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
owns?(key: string): boolean;
|
|
21
|
+
evaluate(): Promise<AlertCondition[]>;
|
|
22
|
+
}
|
|
23
|
+
interface TableOption {
|
|
24
|
+
readonly table?: string;
|
|
25
|
+
readonly database?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Raises `job-dead-letter:<jobType>` for jobs that exhausted their attempts since the last
|
|
29
|
+
* evaluation (any process: the position is kept in `pipework_alert_source_state`; the first
|
|
30
|
+
* evaluation starts from now, so history is not replayed). The condition clears only by the
|
|
31
|
+
* handle's quiet period, so set `resolveAfterQuietMs` on `alerts.create`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function deadLetterSource(options?: TableOption & {
|
|
34
|
+
severity?: AlertSeverity;
|
|
35
|
+
}): AlertSource;
|
|
36
|
+
/** Raises `job-long-running:<jobType>` while a job of that type has been claimed for longer than `olderThanMs`; resolves when none is. */
|
|
37
|
+
export declare function longRunningSource(options: TableOption & {
|
|
38
|
+
olderThanMs: number;
|
|
39
|
+
severity?: AlertSeverity;
|
|
40
|
+
}): AlertSource;
|
|
41
|
+
/**
|
|
42
|
+
* Raises `queue-backlog:<jobType>` while more than `maxPending` jobs of a type are ready to claim, or the
|
|
43
|
+
* oldest has waited more than `maxOldestAgeMs`; resolves when the type is under both.
|
|
44
|
+
*/
|
|
45
|
+
export declare function backlogSource(options: TableOption & {
|
|
46
|
+
maxPending?: number;
|
|
47
|
+
maxOldestAgeMs?: number;
|
|
48
|
+
severity?: AlertSeverity;
|
|
49
|
+
}): AlertSource;
|
|
50
|
+
/** Raises `health:<name>` while a check reports unhealthy or throws; resolves when it is healthy again. Degraded is not a fault. */
|
|
51
|
+
export declare function healthSource(checks: readonly HealthCheck[], options?: {
|
|
52
|
+
severity?: AlertSeverity;
|
|
53
|
+
}): AlertSource;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../../../src/infra/alerts/sources.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC7E;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,QAAQ,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;CACtC;AAED,UAAU,WAAW;IAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE;AAQ7E;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,WAAW,GAAG;IAAE,QAAQ,CAAC,EAAE,aAAa,CAAA;CAAO,GAAG,WAAW,CAiCtG;AAED,0IAA0I;AAC1I,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,aAAa,CAAA;CAAE,GAAG,WAAW,CAqBvH;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,aAAa,CAAA;CAAE,GAAG,WAAW,CA8B5I;AAED,oIAAoI;AACpI,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,EAAE,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,aAAa,CAAA;CAAO,GAAG,WAAW,CAqBpH"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { sql } from '../../data/db/sql.js';
|
|
2
|
+
import { pipeSystem } from '../../data/db/db.js';
|
|
3
|
+
import { assertValidIdentifier, quoteIdentifier } from '../../data/db/identifiers.js';
|
|
4
|
+
function jobsTable(table) {
|
|
5
|
+
const name = table ?? 'pipework_jobs';
|
|
6
|
+
assertValidIdentifier(name, 'queue table');
|
|
7
|
+
return quoteIdentifier(name, 'queue table');
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Raises `job-dead-letter:<jobType>` for jobs that exhausted their attempts since the last
|
|
11
|
+
* evaluation (any process: the position is kept in `pipework_alert_source_state`; the first
|
|
12
|
+
* evaluation starts from now, so history is not replayed). The condition clears only by the
|
|
13
|
+
* handle's quiet period, so set `resolveAfterQuietMs` on `alerts.create`.
|
|
14
|
+
*/
|
|
15
|
+
export function deadLetterSource(options = {}) {
|
|
16
|
+
const jobs = jobsTable(options.table);
|
|
17
|
+
const severity = options.severity ?? 'error';
|
|
18
|
+
const name = 'job-dead-letter';
|
|
19
|
+
return {
|
|
20
|
+
name,
|
|
21
|
+
async evaluate() {
|
|
22
|
+
const db = pipeSystem(options.database);
|
|
23
|
+
const first = await db.execute(sql `
|
|
24
|
+
INSERT INTO pipework_alert_source_state (name, cursor_at) VALUES (${name}, clock_timestamp())
|
|
25
|
+
ON CONFLICT (name) DO NOTHING RETURNING name`);
|
|
26
|
+
if (first.length > 0)
|
|
27
|
+
return [];
|
|
28
|
+
const rows = await db.execute(sql `
|
|
29
|
+
WITH cur AS (SELECT cursor_at FROM pipework_alert_source_state WHERE name = ${name} FOR UPDATE),
|
|
30
|
+
found AS (
|
|
31
|
+
SELECT j.job_type, count(*)::int AS n, max(j.completed_at) AS hi,
|
|
32
|
+
(array_agg(j.error_payload->>'message' ORDER BY j.completed_at DESC))[1] AS last_error
|
|
33
|
+
FROM ${sql.raw(jobs)} j, cur
|
|
34
|
+
WHERE j.status = 'dead_letter' AND j.completed_at > cur.cursor_at
|
|
35
|
+
GROUP BY j.job_type),
|
|
36
|
+
moved AS (
|
|
37
|
+
UPDATE pipework_alert_source_state s SET cursor_at = GREATEST(s.cursor_at, (SELECT max(hi) FROM found))
|
|
38
|
+
WHERE s.name = ${name} AND EXISTS (SELECT 1 FROM found) RETURNING 1)
|
|
39
|
+
SELECT job_type, n, last_error, (SELECT count(*) FROM moved) AS moved FROM found ORDER BY job_type`);
|
|
40
|
+
return rows.map(r => ({
|
|
41
|
+
key: `job-dead-letter:${String(r['job_type'])}`,
|
|
42
|
+
severity,
|
|
43
|
+
message: `${String(r['n'])} ${String(r['job_type'])} job(s) dead-lettered`
|
|
44
|
+
+ (r['last_error'] !== null && r['last_error'] !== undefined ? `; last error: ${String(r['last_error'])}` : ''),
|
|
45
|
+
subject: { entity: 'job', entityId: String(r['job_type']) },
|
|
46
|
+
}));
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Raises `job-long-running:<jobType>` while a job of that type has been claimed for longer than `olderThanMs`; resolves when none is. */
|
|
51
|
+
export function longRunningSource(options) {
|
|
52
|
+
const jobs = jobsTable(options.table);
|
|
53
|
+
const severity = options.severity ?? 'warning';
|
|
54
|
+
return {
|
|
55
|
+
name: 'job-long-running',
|
|
56
|
+
owns: key => key.startsWith('job-long-running:'),
|
|
57
|
+
async evaluate() {
|
|
58
|
+
const rows = await pipeSystem(options.database).execute(sql `
|
|
59
|
+
SELECT job_type, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(claimed_at)) * 1000)::bigint AS oldest_ms
|
|
60
|
+
FROM ${sql.raw(jobs)}
|
|
61
|
+
WHERE status IN ('claimed', 'running')
|
|
62
|
+
AND claimed_at < clock_timestamp() - ${options.olderThanMs} * interval '1 millisecond'
|
|
63
|
+
GROUP BY job_type ORDER BY job_type`);
|
|
64
|
+
return rows.map(r => ({
|
|
65
|
+
key: `job-long-running:${String(r['job_type'])}`,
|
|
66
|
+
severity,
|
|
67
|
+
message: `${String(r['n'])} ${String(r['job_type'])} job(s) running for more than ${String(options.olderThanMs)} ms (oldest ${String(r['oldest_ms'])} ms)`,
|
|
68
|
+
subject: { entity: 'job', entityId: String(r['job_type']) },
|
|
69
|
+
}));
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Raises `queue-backlog:<jobType>` while more than `maxPending` jobs of a type are ready to claim, or the
|
|
75
|
+
* oldest has waited more than `maxOldestAgeMs`; resolves when the type is under both.
|
|
76
|
+
*/
|
|
77
|
+
export function backlogSource(options) {
|
|
78
|
+
if (options.maxPending === undefined && options.maxOldestAgeMs === undefined) {
|
|
79
|
+
throw new Error('[pipework] alerts.source.backlog needs maxPending, maxOldestAgeMs or both.');
|
|
80
|
+
}
|
|
81
|
+
const jobs = jobsTable(options.table);
|
|
82
|
+
const severity = options.severity ?? 'warning';
|
|
83
|
+
const maxPending = options.maxPending ?? null;
|
|
84
|
+
const maxAge = options.maxOldestAgeMs ?? null;
|
|
85
|
+
return {
|
|
86
|
+
name: 'queue-backlog',
|
|
87
|
+
owns: key => key.startsWith('queue-backlog:'),
|
|
88
|
+
async evaluate() {
|
|
89
|
+
const rows = await pipeSystem(options.database).execute(sql `
|
|
90
|
+
SELECT job_type, n, oldest_ms FROM (
|
|
91
|
+
SELECT job_type, count(*)::int AS n,
|
|
92
|
+
(extract(epoch FROM clock_timestamp() - min(queued_at)) * 1000)::bigint AS oldest_ms
|
|
93
|
+
FROM ${sql.raw(jobs)}
|
|
94
|
+
WHERE status = 'pending' AND (next_retry_at IS NULL OR next_retry_at <= clock_timestamp())
|
|
95
|
+
GROUP BY job_type) p
|
|
96
|
+
WHERE (${maxPending}::int IS NOT NULL AND n > ${maxPending}::int)
|
|
97
|
+
OR (${maxAge}::bigint IS NOT NULL AND oldest_ms > ${maxAge}::bigint)
|
|
98
|
+
ORDER BY job_type`);
|
|
99
|
+
return rows.map(r => ({
|
|
100
|
+
key: `queue-backlog:${String(r['job_type'])}`,
|
|
101
|
+
severity,
|
|
102
|
+
message: `${String(r['n'])} ${String(r['job_type'])} job(s) waiting, the oldest for ${String(r['oldest_ms'])} ms`,
|
|
103
|
+
subject: { entity: 'job', entityId: String(r['job_type']) },
|
|
104
|
+
}));
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Raises `health:<name>` while a check reports unhealthy or throws; resolves when it is healthy again. Degraded is not a fault. */
|
|
109
|
+
export function healthSource(checks, options = {}) {
|
|
110
|
+
const severity = options.severity ?? 'error';
|
|
111
|
+
const keys = new Set(checks.map(c => `health:${c.name}`));
|
|
112
|
+
return {
|
|
113
|
+
name: 'health',
|
|
114
|
+
owns: key => keys.has(key),
|
|
115
|
+
async evaluate() {
|
|
116
|
+
const found = [];
|
|
117
|
+
for (const check of checks) {
|
|
118
|
+
let message = null;
|
|
119
|
+
try {
|
|
120
|
+
const result = await check.check();
|
|
121
|
+
if (result.status === 'unhealthy')
|
|
122
|
+
message = `${check.name} is unhealthy${result.message !== undefined ? `: ${result.message}` : ''}`;
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
message = `${check.name} check threw: ${err instanceof Error ? err.message : String(err)}`;
|
|
126
|
+
}
|
|
127
|
+
if (message !== null)
|
|
128
|
+
found.push({ key: `health:${check.name}`, severity, message, subject: { entity: 'health', entityId: check.name } });
|
|
129
|
+
}
|
|
130
|
+
return found;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.js","sourceRoot":"","sources":["../../../src/infra/alerts/sources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface AwsCredentials {
|
|
2
|
+
readonly accessKeyId: string;
|
|
3
|
+
readonly secretAccessKey: string;
|
|
4
|
+
readonly sessionToken?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SigV4Request {
|
|
7
|
+
readonly method: string;
|
|
8
|
+
readonly url: string;
|
|
9
|
+
/** Headers to sign and send, other than host and x-amz-date, which are added. */
|
|
10
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
11
|
+
readonly body: string | Buffer;
|
|
12
|
+
/**
|
|
13
|
+
* The hex SHA-256 of the body, or `UNSIGNED-PAYLOAD`, used in place of hashing `body`.
|
|
14
|
+
* For a streamed S3 body; the caller also sends and signs `x-amz-content-sha256`.
|
|
15
|
+
*/
|
|
16
|
+
readonly payloadHash?: string;
|
|
17
|
+
readonly region: string;
|
|
18
|
+
readonly service: string;
|
|
19
|
+
readonly credentials: AwsCredentials;
|
|
20
|
+
readonly now?: Date;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Signs a request with AWS Signature Version 4 and returns the headers to send:
|
|
24
|
+
* the given ones, `x-amz-date`, `x-amz-security-token` when the credentials carry
|
|
25
|
+
* a session token, and `authorization`. `host` is signed but not returned; the
|
|
26
|
+
* HTTP client sets it from the URL.
|
|
27
|
+
*/
|
|
28
|
+
export declare function signV4(req: SigV4Request): Record<string, string>;
|
|
29
|
+
export interface PresignRequest {
|
|
30
|
+
readonly method: string;
|
|
31
|
+
/** The full URL, query included (e.g. `response-content-disposition`); it is signed with the rest. */
|
|
32
|
+
readonly url: string;
|
|
33
|
+
readonly region: string;
|
|
34
|
+
readonly service: string;
|
|
35
|
+
readonly credentials: AwsCredentials;
|
|
36
|
+
/** 1 to 604800 (seven days). */
|
|
37
|
+
readonly expiresInSeconds: number;
|
|
38
|
+
readonly now?: Date;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Returns `url` with SigV4 query-string signing added (`X-Amz-Algorithm`, `-Credential`, `-Date`,
|
|
42
|
+
* `-Expires`, `-SignedHeaders=host`, `-Security-Token` for a session token, `-Signature`), so a client
|
|
43
|
+
* with no credentials can make the one request until it expires. The payload is `UNSIGNED-PAYLOAD`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function presignV4(req: PresignRequest): string;
|
|
46
|
+
//# sourceMappingURL=sigv4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.d.ts","sourceRoot":"","sources":["../../../src/infra/aws/sigv4.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IAC9B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAA;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAA;CACpB;AAoCD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA8BhE;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,sGAAsG;IACtG,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAA;IACpC,gCAAgC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAyBrD"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash, createHmac } from 'node:crypto';
|
|
2
|
+
function sha256Hex(data) {
|
|
3
|
+
return createHash('sha256').update(data).digest('hex');
|
|
4
|
+
}
|
|
5
|
+
function hmac(key, data) {
|
|
6
|
+
return createHmac('sha256', key).update(data).digest();
|
|
7
|
+
}
|
|
8
|
+
function uriEncode(s) {
|
|
9
|
+
return encodeURIComponent(s).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
10
|
+
}
|
|
11
|
+
function canonicalPathOf(url, service) {
|
|
12
|
+
// S3 encodes each path segment once; every other service encodes the already-encoded path again.
|
|
13
|
+
if (service === 's3')
|
|
14
|
+
return url.pathname.split('/').map(seg => uriEncode(decodeURIComponent(seg))).join('/');
|
|
15
|
+
return url.pathname.split('/').map(uriEncode).join('/');
|
|
16
|
+
}
|
|
17
|
+
function canonicalQueryOf(params) {
|
|
18
|
+
return [...params]
|
|
19
|
+
.map(([k, v]) => [uriEncode(k), uriEncode(v)])
|
|
20
|
+
.sort((a, b) => (a[0] === b[0] ? (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) : (a[0] < b[0] ? -1 : 1)))
|
|
21
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
22
|
+
.join('&');
|
|
23
|
+
}
|
|
24
|
+
function signingKey(secret, day, region, service) {
|
|
25
|
+
return hmac(hmac(hmac(hmac(`AWS4${secret}`, day), region), service), 'aws4_request');
|
|
26
|
+
}
|
|
27
|
+
function amzDate(d) {
|
|
28
|
+
return d.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Signs a request with AWS Signature Version 4 and returns the headers to send:
|
|
32
|
+
* the given ones, `x-amz-date`, `x-amz-security-token` when the credentials carry
|
|
33
|
+
* a session token, and `authorization`. `host` is signed but not returned; the
|
|
34
|
+
* HTTP client sets it from the URL.
|
|
35
|
+
*/
|
|
36
|
+
export function signV4(req) {
|
|
37
|
+
const url = new URL(req.url);
|
|
38
|
+
const stamp = amzDate(req.now ?? new Date());
|
|
39
|
+
const day = stamp.slice(0, 8);
|
|
40
|
+
const headers = {};
|
|
41
|
+
for (const [name, value] of Object.entries(req.headers))
|
|
42
|
+
headers[name.toLowerCase()] = value.trim().replace(/\s+/g, ' ');
|
|
43
|
+
headers['x-amz-date'] = stamp;
|
|
44
|
+
if (req.credentials.sessionToken !== undefined)
|
|
45
|
+
headers['x-amz-security-token'] = req.credentials.sessionToken;
|
|
46
|
+
const signed = { ...headers, host: url.host };
|
|
47
|
+
const names = Object.keys(signed).sort();
|
|
48
|
+
const canonicalHeaders = names.map(n => `${n}:${signed[n]}\n`).join('');
|
|
49
|
+
const signedHeaders = names.join(';');
|
|
50
|
+
const canonicalRequest = [
|
|
51
|
+
req.method.toUpperCase(), canonicalPathOf(url, req.service), canonicalQueryOf(url.searchParams.entries()),
|
|
52
|
+
canonicalHeaders, signedHeaders, req.payloadHash ?? sha256Hex(req.body),
|
|
53
|
+
].join('\n');
|
|
54
|
+
const scope = `${day}/${req.region}/${req.service}/aws4_request`;
|
|
55
|
+
const stringToSign = ['AWS4-HMAC-SHA256', stamp, scope, sha256Hex(canonicalRequest)].join('\n');
|
|
56
|
+
const signature = createHmac('sha256', signingKey(req.credentials.secretAccessKey, day, req.region, req.service))
|
|
57
|
+
.update(stringToSign).digest('hex');
|
|
58
|
+
return {
|
|
59
|
+
...headers,
|
|
60
|
+
authorization: `AWS4-HMAC-SHA256 Credential=${req.credentials.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Returns `url` with SigV4 query-string signing added (`X-Amz-Algorithm`, `-Credential`, `-Date`,
|
|
65
|
+
* `-Expires`, `-SignedHeaders=host`, `-Security-Token` for a session token, `-Signature`), so a client
|
|
66
|
+
* with no credentials can make the one request until it expires. The payload is `UNSIGNED-PAYLOAD`.
|
|
67
|
+
*/
|
|
68
|
+
export function presignV4(req) {
|
|
69
|
+
if (!Number.isInteger(req.expiresInSeconds) || req.expiresInSeconds < 1 || req.expiresInSeconds > 604800) {
|
|
70
|
+
throw new Error('[pipework] presigned URL expiry must be an integer from 1 to 604800 seconds.');
|
|
71
|
+
}
|
|
72
|
+
const url = new URL(req.url);
|
|
73
|
+
const stamp = amzDate(req.now ?? new Date());
|
|
74
|
+
const day = stamp.slice(0, 8);
|
|
75
|
+
const scope = `${day}/${req.region}/${req.service}/aws4_request`;
|
|
76
|
+
url.searchParams.set('X-Amz-Algorithm', 'AWS4-HMAC-SHA256');
|
|
77
|
+
url.searchParams.set('X-Amz-Credential', `${req.credentials.accessKeyId}/${scope}`);
|
|
78
|
+
url.searchParams.set('X-Amz-Date', stamp);
|
|
79
|
+
url.searchParams.set('X-Amz-Expires', String(req.expiresInSeconds));
|
|
80
|
+
url.searchParams.set('X-Amz-SignedHeaders', 'host');
|
|
81
|
+
if (req.credentials.sessionToken !== undefined)
|
|
82
|
+
url.searchParams.set('X-Amz-Security-Token', req.credentials.sessionToken);
|
|
83
|
+
const canonicalRequest = [
|
|
84
|
+
req.method.toUpperCase(), canonicalPathOf(url, req.service), canonicalQueryOf(url.searchParams.entries()),
|
|
85
|
+
`host:${url.host}\n`, 'host', 'UNSIGNED-PAYLOAD',
|
|
86
|
+
].join('\n');
|
|
87
|
+
const stringToSign = ['AWS4-HMAC-SHA256', stamp, scope, sha256Hex(canonicalRequest)].join('\n');
|
|
88
|
+
const signature = createHmac('sha256', signingKey(req.credentials.secretAccessKey, day, req.region, req.service))
|
|
89
|
+
.update(stringToSign).digest('hex');
|
|
90
|
+
// Keep the query exactly as signed: rebuild it with the same encoding the canonical form used.
|
|
91
|
+
const query = canonicalQueryOf(url.searchParams.entries());
|
|
92
|
+
return `${url.origin}${url.pathname}?${query}&X-Amz-Signature=${signature}`;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=sigv4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.js","sourceRoot":"","sources":["../../../src/infra/aws/sigv4.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createMail, normalizeAddress, SEND_JOB, type Mail, type MailConfig, type SendInput, type SendResult, type RenderedEmail, type EmailTemplates } from './mail.js';
|
|
2
2
|
export { ses, devStandIn, type MailProvider, type OutgoingMessage, type SesConfig } from './providers.js';
|
|
3
3
|
export { snsWebhook, type SnsWebhookConfig } from './sns.js';
|
|
4
|
-
export { signV4, type SigV4Request, type AwsCredentials } from '
|
|
4
|
+
export { signV4, type SigV4Request, type AwsCredentials } from '../aws/sigv4.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/infra/email/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAC1G,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACzG,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/infra/email/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAC1G,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACzG,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createMail, normalizeAddress, SEND_JOB } from './mail.js';
|
|
2
2
|
export { ses, devStandIn } from './providers.js';
|
|
3
3
|
export { snsWebhook } from './sns.js';
|
|
4
|
-
export { signV4 } from '
|
|
4
|
+
export { signV4 } from '../aws/sigv4.js';
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/infra/email/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EACJ,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,GAAG,EAAE,UAAU,EAA2D,MAAM,gBAAgB,CAAA;AACzG,OAAO,EAAE,UAAU,EAAyB,MAAM,UAAU,CAAA;AAC5D,OAAO,EAAE,MAAM,EAA0C,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/infra/email/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EACJ,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,GAAG,EAAE,UAAU,EAA2D,MAAM,gBAAgB,CAAA;AACzG,OAAO,EAAE,UAAU,EAAyB,MAAM,UAAU,CAAA;AAC5D,OAAO,EAAE,MAAM,EAA0C,MAAM,iBAAiB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../../src/infra/email/providers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../../src/infra/email/providers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAE7D,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CACnC;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;QAAE,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACvE;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,qFAAqF;IACrF,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAA;IACnD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,oGAAoG;IACpG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,CAsCnD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,CAYzE"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { getBaseLogger } from '../../core/logging/index.js';
|
|
4
|
-
import { signV4 } from '
|
|
4
|
+
import { signV4 } from '../aws/sigv4.js';
|
|
5
5
|
/**
|
|
6
6
|
* Amazon SES v2 provider. Posts to `/v2/email/outbound-emails`, signed with SigV4
|
|
7
7
|
* (no AWS SDK). Credentials come from the app, never from the environment.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../../src/infra/email/providers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAC3D,OAAO,EAAE,MAAM,EAAuB,MAAM,
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../../src/infra/email/providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ export declare function snsStringToSign(msg: SnsMessage): string;
|
|
|
18
18
|
* `pipework_email_suppressions`, which the send job checks; a transient or undetermined
|
|
19
19
|
* bounce is only logged. Each one is a trace step with an `Email:bounce` / `Email:complaint`
|
|
20
20
|
* audit record whose `after` carries `messageTraceId`, the trace of the original send.
|
|
21
|
-
* A suppression is logged at warn as `email suppressed`;
|
|
21
|
+
* A suppression is logged at warn as `email suppressed`; an app raises an alert for it with `alerts.create(...).raise`.
|
|
22
22
|
*
|
|
23
23
|
* Only messages from the configured `topicArns` are accepted (401 otherwise, before any subscription is
|
|
24
24
|
* confirmed or event processed): SNS signs every topic, including an attacker's own.
|
package/dist/infra/email/sns.js
CHANGED
|
@@ -86,7 +86,7 @@ const processEvent = __flowStep({ ref: 'pipework/email:notification' }, async (e
|
|
|
86
86
|
* `pipework_email_suppressions`, which the send job checks; a transient or undetermined
|
|
87
87
|
* bounce is only logged. Each one is a trace step with an `Email:bounce` / `Email:complaint`
|
|
88
88
|
* audit record whose `after` carries `messageTraceId`, the trace of the original send.
|
|
89
|
-
* A suppression is logged at warn as `email suppressed`;
|
|
89
|
+
* A suppression is logged at warn as `email suppressed`; an app raises an alert for it with `alerts.create(...).raise`.
|
|
90
90
|
*
|
|
91
91
|
* Only messages from the configured `topicArns` are accepted (401 otherwise, before any subscription is
|
|
92
92
|
* confirmed or event processed): SNS signs every topic, including an attacker's own.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
/** One stored object as a listing reports it. `key` is relative to the backend's prefix. */
|
|
3
|
+
export interface BackendObject {
|
|
4
|
+
readonly key: string;
|
|
5
|
+
readonly lastModified: Date;
|
|
6
|
+
}
|
|
7
|
+
export interface BackendSignedUrlOptions {
|
|
8
|
+
readonly expiresInSeconds: number;
|
|
9
|
+
readonly disposition: 'attachment' | 'inline';
|
|
10
|
+
readonly filename?: string;
|
|
11
|
+
readonly mediaType: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Where the bytes of stored files live. `storage.s3(...)` and `storage.local(...)`
|
|
15
|
+
* make one; keys are the relative `tenants/<tenantId>/...` names the handle builds.
|
|
16
|
+
*/
|
|
17
|
+
export interface StorageBackend {
|
|
18
|
+
readonly name: 's3' | 'local';
|
|
19
|
+
/** Streams `body` to `key`, replacing any object there. A stream error leaves nothing at `key`. */
|
|
20
|
+
put(key: string, body: AsyncIterable<Buffer | Uint8Array>): Promise<void>;
|
|
21
|
+
exists(key: string): Promise<boolean>;
|
|
22
|
+
copy(from: string, to: string): Promise<void>;
|
|
23
|
+
/** Removes an object; absent is not an error. */
|
|
24
|
+
remove(key: string): Promise<void>;
|
|
25
|
+
/** Streams an object's bytes. */
|
|
26
|
+
read(key: string): Promise<Readable>;
|
|
27
|
+
/** Every object whose key starts with `prefix`, in key order. */
|
|
28
|
+
list(prefix: string): AsyncIterable<BackendObject>;
|
|
29
|
+
/** A time-limited URL that serves the object to a client with no credentials; throws where the backend cannot. */
|
|
30
|
+
signedUrl(key: string, options: BackendSignedUrlOptions): Promise<string>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/infra/storage/backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,4FAA4F;AAC5F,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAA;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,YAAY,GAAG,QAAQ,CAAA;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAA;IAC7B,mGAAmG;IACnG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACrC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,iDAAiD;IACjD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,iCAAiC;IACjC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACpC,iEAAiE;IACjE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,aAAa,CAAC,CAAA;IAClD,kHAAkH;IAClH,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CAC1E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../../../src/infra/storage/backend.ts"],"names":[],"mappings":""}
|