@usebetterdev/audit-core 0.4.0-beta.4 → 0.5.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +280 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +279 -31
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -105,6 +105,13 @@ declare class AuditQueryBuilder {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
type AuditOperation = "INSERT" | "UPDATE" | "DELETE";
|
|
108
|
+
/** Retention policy controlling automatic and CLI-driven purge of old audit logs. */
|
|
109
|
+
interface RetentionPolicy {
|
|
110
|
+
/** Purge entries older than this many days. Must be a positive integer. */
|
|
111
|
+
days: number;
|
|
112
|
+
/** When set, only purge logs for these specific tables. */
|
|
113
|
+
tables?: string[];
|
|
114
|
+
}
|
|
108
115
|
type AuditSeverity = "low" | "medium" | "high" | "critical";
|
|
109
116
|
/** A single audit log entry as stored in the database. */
|
|
110
117
|
interface AuditLog {
|
|
@@ -225,6 +232,8 @@ interface BetterAuditConfig {
|
|
|
225
232
|
onError?: (error: unknown) => void;
|
|
226
233
|
/** Console integration. Pass a BetterConsoleInstance to register audit dashboard endpoints. */
|
|
227
234
|
console?: ConsoleRegistration;
|
|
235
|
+
/** Optional retention policy. When set, enables purge CLI and auto-purge scheduler. */
|
|
236
|
+
retention?: RetentionPolicy;
|
|
228
237
|
}
|
|
229
238
|
/** Input to a manual captureLog call. Context fields override the current AsyncLocalStorage scope. */
|
|
230
239
|
interface CaptureLogInput {
|
|
@@ -288,10 +297,42 @@ interface EnrichmentConfig {
|
|
|
288
297
|
*/
|
|
289
298
|
include?: string[];
|
|
290
299
|
}
|
|
300
|
+
/** Options for `audit.export()`. */
|
|
301
|
+
interface ExportOptions {
|
|
302
|
+
/** Output format. */
|
|
303
|
+
format: "csv" | "json";
|
|
304
|
+
/** Optional query builder to filter exported entries. */
|
|
305
|
+
query?: AuditQueryBuilder;
|
|
306
|
+
/**
|
|
307
|
+
* Output target.
|
|
308
|
+
* - `WritableStream<string>`: WHATWG Web Streams API (Node 18+, Bun, Deno)
|
|
309
|
+
* - `'string'`: buffer in memory and return the full output via `ExportResult`
|
|
310
|
+
*/
|
|
311
|
+
output: WritableStream<string> | "string";
|
|
312
|
+
/** Rows per database round-trip. Default 500. */
|
|
313
|
+
batchSize?: number;
|
|
314
|
+
/** CSV delimiter character (must be exactly one character). Default `','`. */
|
|
315
|
+
csvDelimiter?: string;
|
|
316
|
+
/** JSON output style. `'ndjson'` (default) for streaming, `'array'` for pretty-printed. */
|
|
317
|
+
jsonStyle?: "ndjson" | "array";
|
|
318
|
+
}
|
|
319
|
+
/** Result returned when `audit.export()` completes. */
|
|
320
|
+
interface ExportResult {
|
|
321
|
+
rowCount: number;
|
|
322
|
+
/** Present only when `output` is `'string'`. */
|
|
323
|
+
data?: string;
|
|
324
|
+
}
|
|
291
325
|
interface BetterAuditInstance {
|
|
292
326
|
captureLog(input: CaptureLogInput): Promise<void>;
|
|
293
327
|
/** Returns a fluent query builder. Requires `queryAdapter` in config. */
|
|
294
328
|
query(): AuditQueryBuilder;
|
|
329
|
+
/**
|
|
330
|
+
* Export audit log entries as CSV or JSON.
|
|
331
|
+
*
|
|
332
|
+
* Streams rows in batches via cursor pagination so memory stays flat
|
|
333
|
+
* regardless of export size. Requires `queryLogs` on the database adapter.
|
|
334
|
+
*/
|
|
335
|
+
export(options: ExportOptions): Promise<ExportResult>;
|
|
295
336
|
withContext<T>(context: AuditContext, fn: () => Promise<T>): Promise<T>;
|
|
296
337
|
/**
|
|
297
338
|
* Register an enrichment config for a table/operation pair.
|
|
@@ -304,6 +345,8 @@ interface BetterAuditInstance {
|
|
|
304
345
|
onBeforeLog(hook: BeforeLogHook): () => void;
|
|
305
346
|
/** Register a hook that runs after each log is written. Returns a dispose function to unregister the hook. */
|
|
306
347
|
onAfterLog(hook: AfterLogHook): () => void;
|
|
348
|
+
/** Returns the resolved retention policy, or undefined if none was configured. */
|
|
349
|
+
retentionPolicy(): RetentionPolicy | undefined;
|
|
307
350
|
}
|
|
308
351
|
|
|
309
352
|
declare function betterAudit(config: BetterAuditConfig): BetterAuditInstance;
|
|
@@ -380,6 +423,12 @@ declare const AUDIT_LOG_SCHEMA: AuditLogSchema;
|
|
|
380
423
|
*/
|
|
381
424
|
declare function parseDuration(input: string, referenceDate?: Date): Date;
|
|
382
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Core export engine. Fetches rows in cursor-paginated batches and writes
|
|
428
|
+
* them to the sink as CSV or JSON. Memory stays flat regardless of total rows.
|
|
429
|
+
*/
|
|
430
|
+
declare function runExport(executor: QueryExecutor, options: ExportOptions): Promise<ExportResult>;
|
|
431
|
+
|
|
383
432
|
/** Resolved enrichment config after merging all matching tiers. */
|
|
384
433
|
interface ResolvedEnrichment {
|
|
385
434
|
label?: string;
|
|
@@ -519,4 +568,4 @@ declare function fromHeader(headerName: string): ValueExtractor;
|
|
|
519
568
|
*/
|
|
520
569
|
declare function handleMiddleware(extractor: ContextExtractor, request: Request, next: () => Promise<void>, options?: MiddlewareHandlerOptions): Promise<void>;
|
|
521
570
|
|
|
522
|
-
export { AUDIT_LOG_SCHEMA, type AfterLogHook, type AuditApi, type AuditContext, type AuditDatabaseAdapter, type AuditLog, type AuditLogColumnName, type AuditLogSchema, type AuditOperation, AuditQueryBuilder, type AuditQueryFilters, type AuditQueryResult, type AuditQuerySpec, type AuditSeverity, type AuditStats, type BeforeLogHook, type BetterAuditConfig, type BetterAuditInstance, type CaptureLogInput, type ColumnDefinition, type ColumnType, type ConsoleQueryFilters, type ConsoleQueryResult, type ContextExtractor, type EnrichmentConfig, type EnrichmentDescriptionContext, type EnrichmentSummary, type MiddlewareHandlerOptions, type QueryExecutor, type ResourceFilter, type TimeFilter, type ValueExtractor, betterAudit, createAuditApi, createAuditConsoleEndpoints, fromBearerToken, fromCookie, fromHeader, getAuditContext, handleMiddleware, mergeAuditContext, normalizeInput, parseDuration, runWithAuditContext };
|
|
571
|
+
export { AUDIT_LOG_SCHEMA, type AfterLogHook, type AuditApi, type AuditContext, type AuditDatabaseAdapter, type AuditLog, type AuditLogColumnName, type AuditLogSchema, type AuditOperation, AuditQueryBuilder, type AuditQueryFilters, type AuditQueryResult, type AuditQuerySpec, type AuditSeverity, type AuditStats, type BeforeLogHook, type BetterAuditConfig, type BetterAuditInstance, type CaptureLogInput, type ColumnDefinition, type ColumnType, type ConsoleQueryFilters, type ConsoleQueryResult, type ContextExtractor, type EnrichmentConfig, type EnrichmentDescriptionContext, type EnrichmentSummary, type ExportOptions, type ExportResult, type MiddlewareHandlerOptions, type QueryExecutor, type ResourceFilter, type RetentionPolicy, type TimeFilter, type ValueExtractor, betterAudit, createAuditApi, createAuditConsoleEndpoints, fromBearerToken, fromCookie, fromHeader, getAuditContext, handleMiddleware, mergeAuditContext, normalizeInput, parseDuration, runExport, runWithAuditContext };
|
package/dist/index.js
CHANGED
|
@@ -520,38 +520,214 @@ var AuditQueryBuilder = class _AuditQueryBuilder {
|
|
|
520
520
|
}
|
|
521
521
|
};
|
|
522
522
|
|
|
523
|
-
// src/
|
|
524
|
-
var
|
|
523
|
+
// src/export.ts
|
|
524
|
+
var DEFAULT_BATCH_SIZE = 500;
|
|
525
|
+
var DEFAULT_CSV_DELIMITER = ",";
|
|
526
|
+
var CSV_COLUMNS = [
|
|
525
527
|
"id",
|
|
526
528
|
"timestamp",
|
|
527
529
|
"tableName",
|
|
528
530
|
"operation",
|
|
529
531
|
"recordId",
|
|
530
532
|
"actorId",
|
|
531
|
-
"
|
|
533
|
+
"beforeData",
|
|
534
|
+
"afterData",
|
|
535
|
+
"diff",
|
|
532
536
|
"label",
|
|
533
|
-
"description"
|
|
537
|
+
"description",
|
|
538
|
+
"severity",
|
|
539
|
+
"compliance",
|
|
540
|
+
"notify",
|
|
541
|
+
"reason",
|
|
542
|
+
"metadata",
|
|
543
|
+
"redactedFields"
|
|
534
544
|
];
|
|
535
|
-
|
|
536
|
-
|
|
545
|
+
var JSON_FIELDS = /* @__PURE__ */ new Set([
|
|
546
|
+
"beforeData",
|
|
547
|
+
"afterData",
|
|
548
|
+
"diff",
|
|
549
|
+
"compliance",
|
|
550
|
+
"metadata",
|
|
551
|
+
"redactedFields"
|
|
552
|
+
]);
|
|
553
|
+
function escapeCsvField(value, delimiter) {
|
|
554
|
+
if (value.includes('"') || value.includes(delimiter) || value.includes("\n") || value.includes("\r")) {
|
|
537
555
|
return `"${value.replace(/"/g, '""')}"`;
|
|
538
556
|
}
|
|
539
557
|
return value;
|
|
540
558
|
}
|
|
541
|
-
function
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
559
|
+
function formatCsvValue(log, field, delimiter) {
|
|
560
|
+
const value = log[field];
|
|
561
|
+
if (value === void 0 || value === null) {
|
|
562
|
+
return "";
|
|
563
|
+
}
|
|
564
|
+
if (JSON_FIELDS.has(field)) {
|
|
565
|
+
return escapeCsvField(JSON.stringify(value), delimiter);
|
|
566
|
+
}
|
|
567
|
+
if (value instanceof Date) {
|
|
568
|
+
return escapeCsvField(value.toISOString(), delimiter);
|
|
569
|
+
}
|
|
570
|
+
if (typeof value === "boolean") {
|
|
571
|
+
return value ? "true" : "false";
|
|
572
|
+
}
|
|
573
|
+
return escapeCsvField(String(value), delimiter);
|
|
574
|
+
}
|
|
575
|
+
function createStringSink() {
|
|
576
|
+
const chunks = [];
|
|
577
|
+
return {
|
|
578
|
+
async write(chunk) {
|
|
579
|
+
chunks.push(chunk);
|
|
580
|
+
},
|
|
581
|
+
async finish() {
|
|
582
|
+
return chunks.join("");
|
|
583
|
+
},
|
|
584
|
+
async abort() {
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function createStreamSink(stream) {
|
|
589
|
+
const writer = stream.getWriter();
|
|
590
|
+
return {
|
|
591
|
+
async write(chunk) {
|
|
592
|
+
await writer.write(chunk);
|
|
593
|
+
},
|
|
594
|
+
async finish() {
|
|
595
|
+
await writer.close();
|
|
596
|
+
return void 0;
|
|
597
|
+
},
|
|
598
|
+
async abort(error) {
|
|
599
|
+
await writer.abort(error);
|
|
600
|
+
}
|
|
601
|
+
};
|
|
554
602
|
}
|
|
603
|
+
async function runExport(executor, options) {
|
|
604
|
+
const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
605
|
+
if (batchSize <= 0) {
|
|
606
|
+
throw new Error(`batchSize must be greater than 0, got ${batchSize}`);
|
|
607
|
+
}
|
|
608
|
+
const delimiter = options.csvDelimiter ?? DEFAULT_CSV_DELIMITER;
|
|
609
|
+
if (delimiter.length !== 1) {
|
|
610
|
+
throw new Error("csvDelimiter must be exactly one character");
|
|
611
|
+
}
|
|
612
|
+
const jsonStyle = options.jsonStyle ?? "ndjson";
|
|
613
|
+
const sink = options.output === "string" ? createStringSink() : createStreamSink(options.output);
|
|
614
|
+
const isStringSink = options.output === "string";
|
|
615
|
+
const baseSpec = options.query !== void 0 ? options.query.toSpec() : { filters: {} };
|
|
616
|
+
const totalLimit = baseSpec.limit;
|
|
617
|
+
const spec = { ...baseSpec, limit: batchSize };
|
|
618
|
+
let rowCount = 0;
|
|
619
|
+
try {
|
|
620
|
+
if (options.format === "csv") {
|
|
621
|
+
const header = CSV_COLUMNS.map((col) => escapeCsvField(col, delimiter)).join(delimiter);
|
|
622
|
+
await sink.write(header + "\n");
|
|
623
|
+
let cursor;
|
|
624
|
+
for (; ; ) {
|
|
625
|
+
const currentSpec = cursor !== void 0 ? { ...spec, cursor } : spec;
|
|
626
|
+
const result = await executor(currentSpec);
|
|
627
|
+
if (isStringSink) {
|
|
628
|
+
const lines = [];
|
|
629
|
+
for (const entry of result.entries) {
|
|
630
|
+
if (totalLimit !== void 0 && rowCount >= totalLimit) {
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
const row = CSV_COLUMNS.map((col) => formatCsvValue(entry, col, delimiter)).join(delimiter);
|
|
634
|
+
lines.push(row + "\n");
|
|
635
|
+
rowCount++;
|
|
636
|
+
}
|
|
637
|
+
if (lines.length > 0) {
|
|
638
|
+
await sink.write(lines.join(""));
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
for (const entry of result.entries) {
|
|
642
|
+
if (totalLimit !== void 0 && rowCount >= totalLimit) {
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
const row = CSV_COLUMNS.map((col) => formatCsvValue(entry, col, delimiter)).join(delimiter);
|
|
646
|
+
await sink.write(row + "\n");
|
|
647
|
+
rowCount++;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (totalLimit !== void 0 && rowCount >= totalLimit) {
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
if (result.nextCursor === void 0) {
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
cursor = result.nextCursor;
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
if (jsonStyle === "array") {
|
|
660
|
+
let cursor;
|
|
661
|
+
const entries = [];
|
|
662
|
+
for (; ; ) {
|
|
663
|
+
const currentSpec = cursor !== void 0 ? { ...spec, cursor } : spec;
|
|
664
|
+
const result = await executor(currentSpec);
|
|
665
|
+
for (const entry of result.entries) {
|
|
666
|
+
if (totalLimit !== void 0 && rowCount >= totalLimit) {
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
entries.push(entry);
|
|
670
|
+
rowCount++;
|
|
671
|
+
}
|
|
672
|
+
if (totalLimit !== void 0 && rowCount >= totalLimit) {
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
if (result.nextCursor === void 0) {
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
cursor = result.nextCursor;
|
|
679
|
+
}
|
|
680
|
+
await sink.write(JSON.stringify(entries, null, 2) + "\n");
|
|
681
|
+
} else {
|
|
682
|
+
let cursor;
|
|
683
|
+
for (; ; ) {
|
|
684
|
+
const currentSpec = cursor !== void 0 ? { ...spec, cursor } : spec;
|
|
685
|
+
const result = await executor(currentSpec);
|
|
686
|
+
if (isStringSink) {
|
|
687
|
+
const lines = [];
|
|
688
|
+
for (const entry of result.entries) {
|
|
689
|
+
if (totalLimit !== void 0 && rowCount >= totalLimit) {
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
lines.push(JSON.stringify(entry) + "\n");
|
|
693
|
+
rowCount++;
|
|
694
|
+
}
|
|
695
|
+
if (lines.length > 0) {
|
|
696
|
+
await sink.write(lines.join(""));
|
|
697
|
+
}
|
|
698
|
+
} else {
|
|
699
|
+
for (const entry of result.entries) {
|
|
700
|
+
if (totalLimit !== void 0 && rowCount >= totalLimit) {
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
await sink.write(JSON.stringify(entry) + "\n");
|
|
704
|
+
rowCount++;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (totalLimit !== void 0 && rowCount >= totalLimit) {
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
if (result.nextCursor === void 0) {
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
cursor = result.nextCursor;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
} catch (error) {
|
|
718
|
+
await sink.abort(error);
|
|
719
|
+
throw error;
|
|
720
|
+
}
|
|
721
|
+
const data = await sink.finish();
|
|
722
|
+
if (data !== void 0) {
|
|
723
|
+
return { rowCount, data };
|
|
724
|
+
}
|
|
725
|
+
return { rowCount };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/audit-api.ts
|
|
729
|
+
var VALID_SEVERITIES = /* @__PURE__ */ new Set(["low", "medium", "high", "critical"]);
|
|
730
|
+
var VALID_OPERATIONS = /* @__PURE__ */ new Set(["INSERT", "UPDATE", "DELETE"]);
|
|
555
731
|
function toTimeFilter(date) {
|
|
556
732
|
return { date };
|
|
557
733
|
}
|
|
@@ -568,13 +744,24 @@ function buildQuerySpec(filters, effectiveLimit) {
|
|
|
568
744
|
spec.filters.actorIds = [filters.actorId];
|
|
569
745
|
}
|
|
570
746
|
if (filters.severity !== void 0) {
|
|
747
|
+
if (!VALID_SEVERITIES.has(filters.severity)) {
|
|
748
|
+
throw new Error(
|
|
749
|
+
`Invalid severity "${filters.severity}". Must be one of: low, medium, high, critical`
|
|
750
|
+
);
|
|
751
|
+
}
|
|
571
752
|
spec.filters.severities = [filters.severity];
|
|
572
753
|
}
|
|
573
754
|
if (filters.compliance !== void 0) {
|
|
574
755
|
spec.filters.compliance = [filters.compliance];
|
|
575
756
|
}
|
|
576
757
|
if (filters.operation !== void 0) {
|
|
577
|
-
|
|
758
|
+
const normalized = filters.operation.toUpperCase();
|
|
759
|
+
if (!VALID_OPERATIONS.has(normalized)) {
|
|
760
|
+
throw new Error(
|
|
761
|
+
`Invalid operation "${filters.operation}". Must be one of: INSERT, UPDATE, DELETE`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
spec.filters.operations = [normalized];
|
|
578
765
|
}
|
|
579
766
|
if (filters.since !== void 0) {
|
|
580
767
|
spec.filters.since = toTimeFilter(filters.since);
|
|
@@ -675,17 +862,28 @@ function createAuditApi(adapter, registry, maxQueryLimit) {
|
|
|
675
862
|
return summaries;
|
|
676
863
|
}
|
|
677
864
|
async function exportLogs(filters, format) {
|
|
865
|
+
const queryFn = requireQueryLogs();
|
|
678
866
|
const exportFormat = format ?? "json";
|
|
679
|
-
const
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
867
|
+
const spec = buildQuerySpec(filters ?? {}, effectiveLimit);
|
|
868
|
+
const queryBuilder = new AuditQueryBuilder(
|
|
869
|
+
(s) => queryFn(s),
|
|
870
|
+
spec.filters,
|
|
871
|
+
spec.limit,
|
|
872
|
+
void 0,
|
|
873
|
+
effectiveLimit,
|
|
874
|
+
spec.sortOrder
|
|
875
|
+
);
|
|
876
|
+
const exportOptions = {
|
|
877
|
+
format: exportFormat,
|
|
878
|
+
query: queryBuilder,
|
|
879
|
+
output: "string",
|
|
880
|
+
...exportFormat === "json" && { jsonStyle: "array" }
|
|
881
|
+
};
|
|
882
|
+
const result = await runExport(
|
|
883
|
+
(s) => queryFn(s),
|
|
884
|
+
exportOptions
|
|
885
|
+
);
|
|
886
|
+
return result.data ?? "";
|
|
689
887
|
}
|
|
690
888
|
async function purgeLogs(options) {
|
|
691
889
|
const purgeFn = requirePurgeLogs();
|
|
@@ -921,14 +1119,54 @@ function createAuditConsoleEndpoints(api) {
|
|
|
921
1119
|
];
|
|
922
1120
|
}
|
|
923
1121
|
|
|
1122
|
+
// src/retention.ts
|
|
1123
|
+
function validateRetentionPolicy(policy) {
|
|
1124
|
+
if (!Number.isInteger(policy.days) || !Number.isFinite(policy.days) || policy.days <= 0) {
|
|
1125
|
+
throw new Error(
|
|
1126
|
+
`retention: 'days' must be a positive integer, got ${String(policy.days)}`
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
if (policy.tables !== void 0) {
|
|
1130
|
+
if (!Array.isArray(policy.tables) || policy.tables.length === 0 || policy.tables.some((t) => typeof t !== "string" || t === "")) {
|
|
1131
|
+
throw new Error(
|
|
1132
|
+
"retention: 'tables' must be a non-empty array of non-empty strings"
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
924
1138
|
// src/better-audit.ts
|
|
925
1139
|
function withContext(context, fn) {
|
|
926
1140
|
return runWithAuditContext(context, fn);
|
|
927
1141
|
}
|
|
928
1142
|
function betterAudit(config) {
|
|
1143
|
+
if (config.retention !== void 0) {
|
|
1144
|
+
validateRetentionPolicy(config.retention);
|
|
1145
|
+
}
|
|
929
1146
|
const { database } = config;
|
|
930
1147
|
const auditTables = new Set(config.auditTables);
|
|
1148
|
+
if (config.retention?.tables !== void 0) {
|
|
1149
|
+
const unknown = config.retention.tables.filter((t) => !auditTables.has(t));
|
|
1150
|
+
if (unknown.length > 0) {
|
|
1151
|
+
throw new Error(
|
|
1152
|
+
`retention: 'tables' contains table(s) not in auditTables: ${unknown.join(", ")}. Registered tables: ${[...auditTables].join(", ")}`
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
931
1156
|
const registry = new EnrichmentRegistry();
|
|
1157
|
+
const resolvedRetention = config.retention !== void 0 ? {
|
|
1158
|
+
...config.retention,
|
|
1159
|
+
...config.retention.tables !== void 0 && { tables: [...config.retention.tables] }
|
|
1160
|
+
} : void 0;
|
|
1161
|
+
function retentionPolicy() {
|
|
1162
|
+
if (resolvedRetention === void 0) {
|
|
1163
|
+
return void 0;
|
|
1164
|
+
}
|
|
1165
|
+
return {
|
|
1166
|
+
...resolvedRetention,
|
|
1167
|
+
...resolvedRetention.tables !== void 0 && { tables: [...resolvedRetention.tables] }
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
932
1170
|
const beforeLogHooks = config.beforeLog !== void 0 ? [...config.beforeLog] : [];
|
|
933
1171
|
const afterLogHooks = config.afterLog !== void 0 ? [...config.afterLog] : [];
|
|
934
1172
|
function enrich(table, operation, enrichmentConfig) {
|
|
@@ -1036,6 +1274,15 @@ function betterAudit(config) {
|
|
|
1036
1274
|
config.maxQueryLimit
|
|
1037
1275
|
);
|
|
1038
1276
|
}
|
|
1277
|
+
async function exportLogs(options) {
|
|
1278
|
+
if (database.queryLogs === void 0) {
|
|
1279
|
+
throw new Error(
|
|
1280
|
+
"audit.export() requires a database adapter that implements queryLogs(). Check that your ORM adapter supports querying."
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
const queryLogs = database.queryLogs;
|
|
1284
|
+
return runExport((spec) => queryLogs(spec), options);
|
|
1285
|
+
}
|
|
1039
1286
|
if (config.console) {
|
|
1040
1287
|
const api = createAuditApi(database, registry, config.maxQueryLimit);
|
|
1041
1288
|
const endpoints = createAuditConsoleEndpoints(api);
|
|
@@ -1045,7 +1292,7 @@ function betterAudit(config) {
|
|
|
1045
1292
|
endpoints
|
|
1046
1293
|
});
|
|
1047
1294
|
}
|
|
1048
|
-
return { captureLog, query, withContext, enrich, onBeforeLog, onAfterLog };
|
|
1295
|
+
return { captureLog, query, export: exportLogs, withContext, enrich, onBeforeLog, onAfterLog, retentionPolicy };
|
|
1049
1296
|
}
|
|
1050
1297
|
|
|
1051
1298
|
// src/audit-log-schema.ts
|
|
@@ -1261,6 +1508,7 @@ export {
|
|
|
1261
1508
|
mergeAuditContext,
|
|
1262
1509
|
normalizeInput,
|
|
1263
1510
|
parseDuration,
|
|
1511
|
+
runExport,
|
|
1264
1512
|
runWithAuditContext
|
|
1265
1513
|
};
|
|
1266
1514
|
//# sourceMappingURL=index.js.map
|