@cqrs-ddd/pipeline-audit 0.5.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/COMMERCIAL_LICENSE.txt +34 -0
- package/LICENSE +661 -0
- package/README.md +35 -0
- package/dist/audit.behavior.d.ts +102 -0
- package/dist/audit.behavior.d.ts.map +1 -0
- package/dist/audit.behavior.js +262 -0
- package/dist/audit.behavior.js.map +1 -0
- package/dist/constants/tokens.d.ts +27 -0
- package/dist/constants/tokens.d.ts.map +1 -0
- package/dist/constants/tokens.js +28 -0
- package/dist/constants/tokens.js.map +1 -0
- package/dist/helpers/audit.intent.d.ts +15 -0
- package/dist/helpers/audit.intent.d.ts.map +1 -0
- package/dist/helpers/audit.intent.js +15 -0
- package/dist/helpers/audit.intent.js.map +1 -0
- package/dist/helpers/build-record.d.ts +42 -0
- package/dist/helpers/build-record.d.ts.map +1 -0
- package/dist/helpers/build-record.js +85 -0
- package/dist/helpers/build-record.js.map +1 -0
- package/dist/helpers/json.d.ts +7 -0
- package/dist/helpers/json.d.ts.map +1 -0
- package/dist/helpers/json.js +102 -0
- package/dist/helpers/json.js.map +1 -0
- package/dist/helpers/redact.d.ts +2 -0
- package/dist/helpers/redact.d.ts.map +1 -0
- package/dist/helpers/redact.js +3 -0
- package/dist/helpers/redact.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/audit-options.interface.d.ts +95 -0
- package/dist/interfaces/audit-options.interface.d.ts.map +1 -0
- package/dist/interfaces/audit-options.interface.js +3 -0
- package/dist/interfaces/audit-options.interface.js.map +1 -0
- package/dist/interfaces/audit-record.interface.d.ts +79 -0
- package/dist/interfaces/audit-record.interface.d.ts.map +1 -0
- package/dist/interfaces/audit-record.interface.js +3 -0
- package/dist/interfaces/audit-record.interface.js.map +1 -0
- package/dist/interfaces/audit-sink.interface.d.ts +37 -0
- package/dist/interfaces/audit-sink.interface.d.ts.map +1 -0
- package/dist/interfaces/audit-sink.interface.js +3 -0
- package/dist/interfaces/audit-sink.interface.js.map +1 -0
- package/dist/sinks/log.sink.d.ts +33 -0
- package/dist/sinks/log.sink.d.ts.map +1 -0
- package/dist/sinks/log.sink.js +31 -0
- package/dist/sinks/log.sink.js.map +1 -0
- package/dist/sinks/postgres.sink.d.ts +69 -0
- package/dist/sinks/postgres.sink.d.ts.map +1 -0
- package/dist/sinks/postgres.sink.js +132 -0
- package/dist/sinks/postgres.sink.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize an audit value without silently collapsing or dropping JavaScript
|
|
3
|
+
* values that native JSON.stringify cannot represent faithfully. Tagged objects
|
|
4
|
+
* keep the representation explicit and portable across log and database sinks.
|
|
5
|
+
*/
|
|
6
|
+
export declare function stringifyAuditValue(value: unknown, space?: number): string;
|
|
7
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/helpers/json.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1E"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
/**
|
|
3
|
+
* Serialize an audit value without silently collapsing or dropping JavaScript
|
|
4
|
+
* values that native JSON.stringify cannot represent faithfully. Tagged objects
|
|
5
|
+
* keep the representation explicit and portable across log and database sinks.
|
|
6
|
+
*/
|
|
7
|
+
export function stringifyAuditValue(value, space) {
|
|
8
|
+
return JSON.stringify(normalizeAuditValue(value, new WeakSet()), null, space);
|
|
9
|
+
}
|
|
10
|
+
function normalizeAuditValue(value, ancestors) {
|
|
11
|
+
if (typeof value === 'bigint') {
|
|
12
|
+
return { $type: 'BigInt', value: value.toString() };
|
|
13
|
+
}
|
|
14
|
+
if (typeof value === 'number' && !Number.isFinite(value)) {
|
|
15
|
+
return { $type: 'Number', value: String(value) };
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === 'function') {
|
|
18
|
+
return { $type: 'Function', name: value.name || null };
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === 'symbol') {
|
|
21
|
+
return { $type: 'Symbol', description: value.description ?? null };
|
|
22
|
+
}
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
return { $type: 'Undefined' };
|
|
25
|
+
if (value === null || typeof value !== 'object')
|
|
26
|
+
return value;
|
|
27
|
+
if (ancestors.has(value))
|
|
28
|
+
return '[Circular]';
|
|
29
|
+
if (value instanceof Date)
|
|
30
|
+
return value.toISOString();
|
|
31
|
+
if (value instanceof RegExp) {
|
|
32
|
+
return {
|
|
33
|
+
$type: 'RegExp',
|
|
34
|
+
source: value.source,
|
|
35
|
+
flags: value.flags,
|
|
36
|
+
lastIndex: value.lastIndex,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
ancestors.add(value);
|
|
40
|
+
try {
|
|
41
|
+
if (value instanceof Error) {
|
|
42
|
+
const symbolProperties = normalizeSymbolProperties(value, ancestors);
|
|
43
|
+
return {
|
|
44
|
+
$type: 'Error',
|
|
45
|
+
name: value.name,
|
|
46
|
+
message: value.message,
|
|
47
|
+
stack: value.stack,
|
|
48
|
+
properties: normalizeAuditValue(Object.fromEntries(Object.entries(value)), ancestors),
|
|
49
|
+
...(symbolProperties.length > 0 ? { symbolProperties } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (value instanceof Map) {
|
|
53
|
+
return {
|
|
54
|
+
$type: 'Map',
|
|
55
|
+
entries: Array.from(value, ([key, item]) => [
|
|
56
|
+
normalizeAuditValue(key, ancestors),
|
|
57
|
+
normalizeAuditValue(item, ancestors),
|
|
58
|
+
]),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (value instanceof Set) {
|
|
62
|
+
return {
|
|
63
|
+
$type: 'Set',
|
|
64
|
+
values: Array.from(value, (item) => normalizeAuditValue(item, ancestors)),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
|
68
|
+
const bytes = value instanceof ArrayBuffer
|
|
69
|
+
? new Uint8Array(value)
|
|
70
|
+
: new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
71
|
+
return {
|
|
72
|
+
$type: value.constructor.name,
|
|
73
|
+
bytes: Array.from(bytes),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(value)) {
|
|
77
|
+
return Array.from({ length: value.length }, (_, index) => index in value
|
|
78
|
+
? normalizeAuditValue(value[index], ancestors)
|
|
79
|
+
: { $type: 'ArrayHole' });
|
|
80
|
+
}
|
|
81
|
+
const properties = Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
82
|
+
key,
|
|
83
|
+
normalizeAuditValue(item, ancestors),
|
|
84
|
+
]));
|
|
85
|
+
const symbolProperties = normalizeSymbolProperties(value, ancestors);
|
|
86
|
+
return symbolProperties.length > 0
|
|
87
|
+
? { $type: 'Object', properties, symbolProperties }
|
|
88
|
+
: properties;
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
ancestors.delete(value);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function normalizeSymbolProperties(value, ancestors) {
|
|
95
|
+
return Object.getOwnPropertySymbols(value)
|
|
96
|
+
.filter((key) => Object.getOwnPropertyDescriptor(value, key)?.enumerable === true)
|
|
97
|
+
.map((key) => [
|
|
98
|
+
normalizeAuditValue(key, ancestors),
|
|
99
|
+
normalizeAuditValue(Reflect.get(value, key), ancestors),
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/helpers/json.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/helpers/redact.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,QAAQ,EACR,WAAW,GACZ,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/helpers/redact.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EACL,mBAAmB,EACnB,QAAQ,EACR,WAAW,GACZ,MAAM,0BAA0B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { AUDIT_RECORD_ITEM, AUDIT_RECORD_ITEM_TOKEN, AUDIT_START_RECORD_ITEM_TOKEN, AuditBehavior, } from './audit.behavior.js';
|
|
2
|
+
export { AUDIT_OUTCOMES, AUDIT_REQUEST_KINDS, AUDIT_SEVERITY, } from './constants/tokens.js';
|
|
3
|
+
export { type AuditIntentOptions, audit, } from './helpers/audit.intent.js';
|
|
4
|
+
export type { BuildAuditRecordInput, BuildAuditStartRecordInput, } from './helpers/build-record.js';
|
|
5
|
+
export { buildAuditRecord, buildAuditStartRecord, } from './helpers/build-record.js';
|
|
6
|
+
export { DEFAULT_REDACT_KEYS, REDACTED, redactValue, } from './helpers/redact.js';
|
|
7
|
+
export type { AuditActorFactory, AuditBehaviorOptions, AuditMetadataFactory, AuditRedactor, } from './interfaces/audit-options.interface.js';
|
|
8
|
+
export type { AuditActor, AuditError, AuditOutcome, AuditRecord, AuditRequestKind, AuditSeverity, AuditStartRecord, } from './interfaces/audit-record.interface.js';
|
|
9
|
+
export type { AuditSink } from './interfaces/audit-sink.interface.js';
|
|
10
|
+
export { type AuditLoggerLike, LogAuditSink, type LogAuditSinkOptions, } from './sinks/log.sink.js';
|
|
11
|
+
export { createAuditTableSql, PostgresAuditSink, type PostgresAuditSinkOptions, type PostgresQueryableLike, } from './sinks/postgres.sink.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,6BAA6B,EAC7B,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,cAAc,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,GACN,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,mBAAmB,EACnB,QAAQ,EACR,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,GACd,MAAM,yCAAyC,CAAC;AACjD,YAAY,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,wCAAwC,CAAC;AAChD,YAAY,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EACL,KAAK,eAAe,EACpB,YAAY,EACZ,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
export { AUDIT_RECORD_ITEM, AUDIT_RECORD_ITEM_TOKEN, AUDIT_START_RECORD_ITEM_TOKEN, AuditBehavior, } from './audit.behavior.js';
|
|
3
|
+
export { AUDIT_OUTCOMES, AUDIT_REQUEST_KINDS, AUDIT_SEVERITY, } from './constants/tokens.js';
|
|
4
|
+
export { audit, } from './helpers/audit.intent.js';
|
|
5
|
+
export { buildAuditRecord, buildAuditStartRecord, } from './helpers/build-record.js';
|
|
6
|
+
export { DEFAULT_REDACT_KEYS, REDACTED, redactValue, } from './helpers/redact.js';
|
|
7
|
+
export { LogAuditSink, } from './sinks/log.sink.js';
|
|
8
|
+
export { createAuditTableSql, PostgresAuditSink, } from './sinks/postgres.sink.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,6BAA6B,EAC7B,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,cAAc,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,KAAK,GACN,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EACL,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,mBAAmB,EACnB,QAAQ,EACR,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAiB7B,OAAO,EAEL,YAAY,GAEb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,EACnB,iBAAiB,GAGlB,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { IPipelineContext } from '@cqrs-ddd/pipeline';
|
|
2
|
+
import type { AuditActor, AuditRequestKind, AuditSeverity } from './audit-record.interface.js';
|
|
3
|
+
/** Resolves the acting principal from the pipeline context. */
|
|
4
|
+
export type AuditActorFactory = (context: IPipelineContext) => AuditActor | undefined;
|
|
5
|
+
/** Produces extra, request-aware metadata to attach to an audit record. */
|
|
6
|
+
export type AuditMetadataFactory = (context: IPipelineContext) => Record<string, unknown>;
|
|
7
|
+
/** Custom redactor replacing a raw payload/response with a safe-to-store value. */
|
|
8
|
+
export type AuditRedactor = (value: unknown) => unknown;
|
|
9
|
+
/**
|
|
10
|
+
* Per-handler (and constructor-default) options for {@link AuditBehavior}.
|
|
11
|
+
*
|
|
12
|
+
* Supplied per handler via `pipeline.wrap(options, [AuditBehavior, { ... }])`,
|
|
13
|
+
* shallow-merged over the constructor defaults (handler keys win).
|
|
14
|
+
*
|
|
15
|
+
* @example Audit a sensitive login command
|
|
16
|
+
* ```ts
|
|
17
|
+
* class LoginHandler {
|
|
18
|
+
* @pipeline.wrap({ kind: 'command' }, [AuditBehavior, {
|
|
19
|
+
* action: 'auth.login',
|
|
20
|
+
* severity: 'medium',
|
|
21
|
+
* redactKeys: ['code'],
|
|
22
|
+
* actor: (ctx) => {
|
|
23
|
+
* const command = ctx.request as LoginCommand;
|
|
24
|
+
* return { id: command.email, email: command.email };
|
|
25
|
+
* },
|
|
26
|
+
* }])
|
|
27
|
+
* async handle(command: LoginCommand) {}
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export interface AuditBehaviorOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Logical action name recorded on the entry (e.g. `user.create`).
|
|
34
|
+
* Default: `context.requestName`.
|
|
35
|
+
*/
|
|
36
|
+
action?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Severity recorded on the entry. Default: `'medium'`, or `'low'` for queries.
|
|
39
|
+
*/
|
|
40
|
+
severity?: AuditSeverity;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the acting principal — typically reads an id from `context.items`
|
|
43
|
+
* set by an upstream auth behavior, e.g.
|
|
44
|
+
* `actor: (c) => ({ id: c.items.get('currentUserId') })`.
|
|
45
|
+
*/
|
|
46
|
+
actor?: AuditActorFactory;
|
|
47
|
+
/**
|
|
48
|
+
* Record the (redacted) request payload. Default `true`. Set `false` for
|
|
49
|
+
* high-volume or sensitive handlers where the action alone is enough.
|
|
50
|
+
*/
|
|
51
|
+
captureRequest?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Record the (redacted) handler response. Default `false` — responses are
|
|
54
|
+
* often large and rarely needed for an audit trail.
|
|
55
|
+
*/
|
|
56
|
+
captureResponse?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Request kinds to audit. Default `['command']`: queries change nothing, and
|
|
59
|
+
* a domain event follows a command that is already audited. Example:
|
|
60
|
+
* `['command', 'query']` to also audit reads.
|
|
61
|
+
*/
|
|
62
|
+
captureKinds?: AuditRequestKind[];
|
|
63
|
+
/**
|
|
64
|
+
* Case-insensitive payload/response field names whose values are masked with
|
|
65
|
+
* `'[REDACTED]'` before storage. Merged with the built-in defaults
|
|
66
|
+
* (`password`, `token`, `secret`, …). Set `redact` for full control.
|
|
67
|
+
*/
|
|
68
|
+
redactKeys?: string[];
|
|
69
|
+
/**
|
|
70
|
+
* Full custom redaction of the payload/response, replacing the built-in
|
|
71
|
+
* key-masking. Receives the raw value, returns the safe-to-store value.
|
|
72
|
+
*/
|
|
73
|
+
redact?: AuditRedactor;
|
|
74
|
+
/** Produce extra metadata to merge into the audit record. */
|
|
75
|
+
metadata?: AuditMetadataFactory;
|
|
76
|
+
/**
|
|
77
|
+
* Write a pending start record before the handler runs, when the sink
|
|
78
|
+
* implements `begin`. Default `true`. Set `false` to save that write when a
|
|
79
|
+
* lost record is acceptable.
|
|
80
|
+
*/
|
|
81
|
+
recordStart?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Include the error stack trace on failure records. Default `true`.
|
|
84
|
+
*/
|
|
85
|
+
includeStack?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* When record construction (actor/metadata/redactor factories) or the sink itself throws,
|
|
88
|
+
* allow the request to continue (`true`, default) or fail a successful request with the
|
|
89
|
+
* audit error (`false`). A handler error is always rethrown unchanged, and an audit failure
|
|
90
|
+
* on that path is only logged. Fail-open favors availability; fail-closed favors a
|
|
91
|
+
* guaranteed audit trail.
|
|
92
|
+
*/
|
|
93
|
+
failOpen?: boolean;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=audit-options.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-options.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/audit-options.interface.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAChB,aAAa,EACd,MAAM,6BAA6B,CAAC;AAErC,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAC9B,OAAO,EAAE,gBAAgB,KACtB,UAAU,GAAG,SAAS,CAAC;AAE5B,2EAA2E;AAC3E,MAAM,MAAM,oBAAoB,GAAG,CACjC,OAAO,EAAE,gBAAgB,KACtB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7B,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB;;;;OAIG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAClC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-options.interface.js","sourceRoot":"","sources":["../../src/interfaces/audit-options.interface.ts"],"names":[],"mappings":"AAAA,sEAAsE"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Request kinds as classified by the pipeline. */
|
|
2
|
+
export type AuditRequestKind = 'command' | 'query' | 'event' | 'unknown';
|
|
3
|
+
/** Whether the audited operation succeeded or threw. */
|
|
4
|
+
export type AuditOutcome = 'success' | 'failure';
|
|
5
|
+
/** Relative importance of an audited action, for filtering / alerting. */
|
|
6
|
+
export type AuditSeverity = 'low' | 'medium' | 'high' | 'critical';
|
|
7
|
+
/**
|
|
8
|
+
* The principal that triggered the audited action — resolved from the pipeline
|
|
9
|
+
* context (e.g. read from `context.items` populated by an upstream auth
|
|
10
|
+
* behavior). `id` is conventional; arbitrary extra fields are allowed.
|
|
11
|
+
*/
|
|
12
|
+
export interface AuditActor {
|
|
13
|
+
/** Stable identifier of the actor (user id, service account, api key id). */
|
|
14
|
+
id?: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
/** Serializable description of a failure captured in an audit record. */
|
|
18
|
+
export interface AuditError {
|
|
19
|
+
/** Error class name (e.g. `ForbiddenException`), or `'unknown'` for non-Error throws. */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Error message. */
|
|
22
|
+
message: string;
|
|
23
|
+
/** Stack trace, unless suppressed via `includeStack: false`. */
|
|
24
|
+
stack?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A single audit-trail entry for one pipeline operation (who did what, when,
|
|
28
|
+
* and with what outcome), forwarded to an
|
|
29
|
+
* {@link AuditSink}.
|
|
30
|
+
*
|
|
31
|
+
* Unlike a dead letter (failures only), an audit record is written for **both**
|
|
32
|
+
* successful and failed operations, so denied/rejected attempts are captured too.
|
|
33
|
+
*/
|
|
34
|
+
export interface AuditRecord {
|
|
35
|
+
/** Unique id for this entry (UUIDv7, so ids sort in start order). */
|
|
36
|
+
id: string;
|
|
37
|
+
/** Correlation ID of the pipeline run (for cross-system tracing). */
|
|
38
|
+
correlationId: string;
|
|
39
|
+
/** Active tenant identifier if execution occurred within a multi-tenant context. */
|
|
40
|
+
tenantId?: string;
|
|
41
|
+
/** Logical action name, e.g. `user.create`. Defaults to the request name. */
|
|
42
|
+
action: string;
|
|
43
|
+
/** Severity of the action. Defaults to `'medium'` (`'low'` for queries). */
|
|
44
|
+
severity: AuditSeverity;
|
|
45
|
+
/** Whether the operation succeeded or failed. */
|
|
46
|
+
outcome: AuditOutcome;
|
|
47
|
+
/** The principal that performed the action, if resolvable. */
|
|
48
|
+
actor?: AuditActor;
|
|
49
|
+
/** Whether the request was a command, query, event, or unknown. */
|
|
50
|
+
requestKind: AuditRequestKind;
|
|
51
|
+
/** Request class name, e.g. `CreateUserCommand`. */
|
|
52
|
+
requestName: string;
|
|
53
|
+
/** Handler class name, e.g. `CreateUserHandler`. */
|
|
54
|
+
handlerName: string;
|
|
55
|
+
/** The request payload (redacted), unless `captureRequest: false`. */
|
|
56
|
+
payload?: unknown;
|
|
57
|
+
/** The handler response (redacted), only when `captureResponse: true`. */
|
|
58
|
+
response?: unknown;
|
|
59
|
+
/** Error details — present only when `outcome` is `'failure'`. */
|
|
60
|
+
error?: AuditError;
|
|
61
|
+
/** Wall-clock duration of the operation in milliseconds. */
|
|
62
|
+
durationMs: number;
|
|
63
|
+
/** ISO-8601 timestamp of when the operation started. */
|
|
64
|
+
timestamp: string;
|
|
65
|
+
/** Metadata from the `metadata` factory, plus `tenantId` when present. */
|
|
66
|
+
metadata?: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The record written before the handler runs, when the sink implements
|
|
70
|
+
* {@link AuditSink.begin}. Its `outcome` stays `'pending'` until the final
|
|
71
|
+
* {@link AuditRecord}, written under the same `id`, replaces it. A record still
|
|
72
|
+
* pending after the process stopped marks an attempt whose outcome is unknown:
|
|
73
|
+
* the handler may or may not have completed.
|
|
74
|
+
*/
|
|
75
|
+
export type AuditStartRecord = Omit<AuditRecord, 'outcome' | 'response' | 'error' | 'durationMs'> & {
|
|
76
|
+
/** Always `'pending'`: the operation has not finished. */
|
|
77
|
+
outcome: 'pending';
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=audit-record.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-record.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/audit-record.interface.ts"],"names":[],"mappings":"AAEA,mDAAmD;AACnD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;AAEzE,wDAAwD;AACxD,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;AAEjD,0EAA0E;AAC1E,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAEnE;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,aAAa,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,QAAQ,EAAE,aAAa,CAAC;IACxB,iDAAiD;IACjD,OAAO,EAAE,YAAY,CAAC;IACtB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,mEAAmE;IACnE,WAAW,EAAE,gBAAgB,CAAC;IAC9B,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,WAAW,EACX,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,YAAY,CAChD,GAAG;IACF,0DAA0D;IAC1D,OAAO,EAAE,SAAS,CAAC;CACpB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-record.interface.js","sourceRoot":"","sources":["../../src/interfaces/audit-record.interface.ts"],"names":[],"mappings":"AAAA,sEAAsE"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AuditRecord, AuditStartRecord } from './audit-record.interface.js';
|
|
2
|
+
/**
|
|
3
|
+
* Backend-agnostic sink for audit records — the single seam every storage
|
|
4
|
+
* backend implements.
|
|
5
|
+
*
|
|
6
|
+
* Implement this once per backend; the bundled implementations are
|
|
7
|
+
* {@link LogAuditSink} (default, zero-dependency) and
|
|
8
|
+
* {@link PostgresAuditSink}. Because {@link AuditBehavior} depends only on this
|
|
9
|
+
* interface, swapping the backend (Postgres, an event store, Kafka, an HTTP
|
|
10
|
+
* collector, …) is a one-line change: the sink passed to its constructor.
|
|
11
|
+
*/
|
|
12
|
+
export interface AuditSink {
|
|
13
|
+
/**
|
|
14
|
+
* Persist / publish a single audit record.
|
|
15
|
+
*
|
|
16
|
+
* Implementations should be resilient. A throw here is logged by the
|
|
17
|
+
* behavior; with `failOpen: false` it also fails a successful request. A
|
|
18
|
+
* handler error is always rethrown unchanged.
|
|
19
|
+
*
|
|
20
|
+
* @param record - The completed audit entry to forward.
|
|
21
|
+
*/
|
|
22
|
+
write(record: AuditRecord): Promise<void> | void;
|
|
23
|
+
/**
|
|
24
|
+
* Persist the start of an operation, before its handler runs. Optional:
|
|
25
|
+
* when implemented, {@link AuditBehavior} calls it first (unless
|
|
26
|
+
* `recordStart: false`), then calls {@link write} with the final record
|
|
27
|
+
* under the same `id`, which must replace this one. An attempt interrupted
|
|
28
|
+
* by a process stop then stays visible as `'pending'` instead of being lost.
|
|
29
|
+
*
|
|
30
|
+
* A throw here is logged by the behavior; with `failOpen: false` it also
|
|
31
|
+
* stops the request before its handler runs.
|
|
32
|
+
*
|
|
33
|
+
* @param record - The pending entry for the operation about to run.
|
|
34
|
+
*/
|
|
35
|
+
begin?(record: AuditStartRecord): Promise<void> | void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=audit-sink.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-sink.interface.d.ts","sourceRoot":"","sources":["../../src/interfaces/audit-sink.interface.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EACjB,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;;;;OAQG;IACH,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAEjD;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-sink.interface.js","sourceRoot":"","sources":["../../src/interfaces/audit-sink.interface.ts"],"names":[],"mappings":"AAAA,sEAAsE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AuditRecord } from '../interfaces/audit-record.interface.js';
|
|
2
|
+
import type { AuditSink } from '../interfaces/audit-sink.interface.js';
|
|
3
|
+
/** Minimal structural logger satisfied by the NestJS `Logger` and `console`. */
|
|
4
|
+
export interface AuditLoggerLike {
|
|
5
|
+
log(message: string): void;
|
|
6
|
+
warn(message: string): void;
|
|
7
|
+
}
|
|
8
|
+
/** Options for {@link LogAuditSink}. */
|
|
9
|
+
export interface LogAuditSinkOptions {
|
|
10
|
+
/** Logger to write to. Default: the global `console`. */
|
|
11
|
+
logger?: AuditLoggerLike;
|
|
12
|
+
/** Pretty-print the JSON record over multiple lines. Default `false`. */
|
|
13
|
+
pretty?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Zero-dependency {@link AuditSink} that writes each record as a JSON line to a
|
|
17
|
+
* logger (the global `console` by default). The default sink — ideal for local
|
|
18
|
+
* development, tests, or shipping audit logs through your existing log pipeline.
|
|
19
|
+
*
|
|
20
|
+
* Failures are routed to `warn`, successes to `log`, so they are easy to filter.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* * new LogAuditSink({ logger: console, pretty: true });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class LogAuditSink implements AuditSink {
|
|
28
|
+
private readonly logger;
|
|
29
|
+
private readonly pretty;
|
|
30
|
+
constructor(options?: LogAuditSinkOptions);
|
|
31
|
+
write(record: AuditRecord): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=log.sink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.sink.d.ts","sourceRoot":"","sources":["../../src/sinks/log.sink.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uCAAuC,CAAC;AAEvE,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,yDAAyD;IACzD,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IAEjC,YAAY,OAAO,GAAE,mBAAwB,EAG5C;IAED,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAQ/B;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* Copyright (C) 2026-present Aristotelis — see repository license. */
|
|
2
|
+
import { stringifyAuditValue } from '../helpers/json.js';
|
|
3
|
+
/**
|
|
4
|
+
* Zero-dependency {@link AuditSink} that writes each record as a JSON line to a
|
|
5
|
+
* logger (the global `console` by default). The default sink — ideal for local
|
|
6
|
+
* development, tests, or shipping audit logs through your existing log pipeline.
|
|
7
|
+
*
|
|
8
|
+
* Failures are routed to `warn`, successes to `log`, so they are easy to filter.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* * new LogAuditSink({ logger: console, pretty: true });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export class LogAuditSink {
|
|
16
|
+
logger;
|
|
17
|
+
pretty;
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.logger = options.logger ?? console;
|
|
20
|
+
this.pretty = options.pretty ?? false;
|
|
21
|
+
}
|
|
22
|
+
write(record) {
|
|
23
|
+
const line = stringifyAuditValue(record, this.pretty ? 2 : undefined);
|
|
24
|
+
if (record.outcome === 'failure') {
|
|
25
|
+
this.logger.warn(line);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this.logger.log(line);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=log.sink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.sink.js","sourceRoot":"","sources":["../../src/sinks/log.sink.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAkBzD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,YAAY;IACN,MAAM,CAAkB;IACxB,MAAM,CAAU;IAEjC,YAAY,OAAO,GAAwB,EAAE;QAC3C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,MAAmB;QACvB,MAAM,IAAI,GAAG,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAEtE,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { AuditRecord, AuditStartRecord } from '../interfaces/audit-record.interface.js';
|
|
2
|
+
import type { AuditSink } from '../interfaces/audit-sink.interface.js';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural shape of a `pg` `Pool` / `Client`. Declared locally so this
|
|
5
|
+
* package does not hard-depend on `pg` — a real `Pool` or `Client` satisfies it.
|
|
6
|
+
* Add it in your app: `pnpm add pg`.
|
|
7
|
+
*/
|
|
8
|
+
export interface PostgresQueryableLike {
|
|
9
|
+
query(text: string, values?: unknown[]): Promise<unknown>;
|
|
10
|
+
}
|
|
11
|
+
/** Options for {@link PostgresAuditSink}. */
|
|
12
|
+
export interface PostgresAuditSinkOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Destination table, optionally schema-qualified (e.g. `audit.audit_log`).
|
|
15
|
+
* Default `'audit_log'`. Validated as a safe SQL identifier.
|
|
16
|
+
*/
|
|
17
|
+
table?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* SQL to create the audit table. Run once in a migration.
|
|
21
|
+
*
|
|
22
|
+
* A row whose `outcome` is `'pending'` is an operation that started and has no
|
|
23
|
+
* final record: `duration_ms` and `completed_at` stay null. An old pending row
|
|
24
|
+
* marks an attempt interrupted by a process stop, whose outcome is unknown:
|
|
25
|
+
*
|
|
26
|
+
* ```sql
|
|
27
|
+
* SELECT * FROM audit_log
|
|
28
|
+
* WHERE outcome = 'pending' AND occurred_at < now() - interval '1 hour';
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @param table - Table name (validated). Default `'audit_log'`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createAuditTableSql(table?: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* {@link AuditSink} backed by **Postgres** (`pg`) — a drop-in replacement for
|
|
36
|
+
* the console sink. {@link begin} inserts a pending row when an operation
|
|
37
|
+
* starts, and {@link write} completes it under the same id (or inserts it, when
|
|
38
|
+
* no pending row exists), so an operation interrupted by a process stop stays
|
|
39
|
+
* visible as `'pending'`.
|
|
40
|
+
*
|
|
41
|
+
* Create the table once with {@link createAuditTableSql}. The table name is
|
|
42
|
+
* validated as a plain SQL identifier (it is interpolated, not parameterized);
|
|
43
|
+
* all record values are passed as bound parameters. A NUL character or a lone
|
|
44
|
+
* surrogate, which `jsonb` rejects, is stored as U+FFFD (see
|
|
45
|
+
* `toPostgresJson`), so one such character cannot lose the whole record.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { Pool } from 'pg';
|
|
50
|
+
* const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
51
|
+
* await pool.query(createAuditTableSql());
|
|
52
|
+
* const sink = new PostgresAuditSink(pool);
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare class PostgresAuditSink implements AuditSink {
|
|
56
|
+
private readonly db;
|
|
57
|
+
private readonly beginSql;
|
|
58
|
+
private readonly writeSql;
|
|
59
|
+
constructor(db: PostgresQueryableLike, options?: PostgresAuditSinkOptions);
|
|
60
|
+
/** Inserts the pending row of an operation about to run. */
|
|
61
|
+
begin(record: AuditStartRecord): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Inserts the final row, or completes the pending row that {@link begin}
|
|
64
|
+
* inserted under the same id.
|
|
65
|
+
*/
|
|
66
|
+
write(record: AuditRecord): Promise<void>;
|
|
67
|
+
private values;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=postgres.sink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.sink.d.ts","sourceRoot":"","sources":["../../src/sinks/postgres.sink.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EACjB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uCAAuC,CAAC;AAEvE;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAED,6CAA6C;AAC7C,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,SAAc,GAAG,MAAM,CAoB/D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,iBAAkB,YAAW,SAAS;IAK/C,OAAO,CAAC,QAAQ,CAAC,EAAE;IAJrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,YACmB,EAAE,EAAE,qBAAqB,EAC1C,OAAO,GAAE,wBAA6B,EAmBvC;IAED,4DAA4D;IACtD,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnD;IAED;;;OAGG;IACG,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9C;IAED,OAAO,CAAC,MAAM;CA6Bf"}
|