@usebetterdev/audit-core 0.4.0-beta.2 → 0.4.0-beta.4

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 CHANGED
@@ -733,17 +733,16 @@ function createAuditApi(adapter, registry, maxQueryLimit) {
733
733
  return { queryLogs, getLog, getStats, getEnrichments, exportLogs, purgeLogs };
734
734
  }
735
735
 
736
- // src/console-endpoints.ts
737
- var MAX_PARAM_LENGTH = 1e3;
738
- function exceedsMaxLength(value) {
739
- return value !== void 0 && value.length > MAX_PARAM_LENGTH;
736
+ // ../../shared/console-utils/src/index.ts
737
+ function isPlainObject(value) {
738
+ return typeof value === "object" && value !== null && !Array.isArray(value);
740
739
  }
741
- function parsePositiveInt(value, max) {
740
+ function parseBoundedInt(value, min, max) {
742
741
  if (value === void 0) {
743
742
  return void 0;
744
743
  }
745
744
  const parsed = Number(value);
746
- if (!Number.isFinite(parsed) || parsed <= 0) {
745
+ if (!Number.isFinite(parsed) || parsed < min) {
747
746
  return void 0;
748
747
  }
749
748
  return Math.min(Math.floor(parsed), max);
@@ -758,10 +757,22 @@ function parseIsoDate(value) {
758
757
  }
759
758
  return date;
760
759
  }
760
+
761
+ // src/console-endpoints.ts
762
+ var MAX_PARAM_LENGTH = 1e3;
763
+ function exceedsMaxLength(value) {
764
+ return value !== void 0 && value.length > MAX_PARAM_LENGTH;
765
+ }
766
+ function hasLongQueryParam(query) {
767
+ return exceedsMaxLength(query.tableName) || exceedsMaxLength(query.actorId) || exceedsMaxLength(query.cursor) || exceedsMaxLength(query.operation) || exceedsMaxLength(query.severity) || exceedsMaxLength(query.compliance) || exceedsMaxLength(query.search);
768
+ }
761
769
  function parseConsoleQueryFilters(query) {
762
770
  const filters = {};
763
- const limit = parsePositiveInt(query.limit, 1e3);
764
- if (limit !== void 0) {
771
+ if (query.limit !== void 0) {
772
+ const limit = parseBoundedInt(query.limit, 1, 1e3);
773
+ if (limit === void 0) {
774
+ return { error: "Invalid 'limit': must be a positive integer (max 1000)" };
775
+ }
765
776
  filters.limit = limit;
766
777
  }
767
778
  if (query.tableName !== void 0) {
@@ -785,18 +796,27 @@ function parseConsoleQueryFilters(query) {
785
796
  if (query.cursor !== void 0) {
786
797
  filters.cursor = query.cursor;
787
798
  }
788
- const since = parseIsoDate(query.since);
789
- if (since !== void 0) {
799
+ if (query.since !== void 0) {
800
+ const since = parseIsoDate(query.since);
801
+ if (since === void 0) {
802
+ return { error: "Invalid 'since': must be an ISO-8601 date" };
803
+ }
790
804
  filters.since = since;
791
805
  }
792
- const until = parseIsoDate(query.until);
793
- if (until !== void 0) {
806
+ if (query.until !== void 0) {
807
+ const until = parseIsoDate(query.until);
808
+ if (until === void 0) {
809
+ return { error: "Invalid 'until': must be an ISO-8601 date" };
810
+ }
794
811
  filters.until = until;
795
812
  }
796
- return filters;
813
+ return { filters };
797
814
  }
798
- function hasLongQueryParam(query) {
799
- return exceedsMaxLength(query.tableName) || exceedsMaxLength(query.actorId) || exceedsMaxLength(query.cursor) || exceedsMaxLength(query.operation) || exceedsMaxLength(query.severity) || exceedsMaxLength(query.compliance) || exceedsMaxLength(query.search);
815
+ function serializeLog(log) {
816
+ return {
817
+ ...log,
818
+ timestamp: log.timestamp.toISOString()
819
+ };
800
820
  }
801
821
  function createAuditConsoleEndpoints(api) {
802
822
  return [
@@ -809,9 +829,19 @@ function createAuditConsoleEndpoints(api) {
809
829
  if (hasLongQueryParam(request.query)) {
810
830
  return { status: 400, body: { error: "Query parameter exceeds maximum length" } };
811
831
  }
812
- const filters = parseConsoleQueryFilters(request.query);
813
- const result = await api.queryLogs(filters);
814
- return { status: 200, body: result };
832
+ const parsed = parseConsoleQueryFilters(request.query);
833
+ if ("error" in parsed) {
834
+ return { status: 400, body: { error: parsed.error } };
835
+ }
836
+ const result = await api.queryLogs(parsed.filters);
837
+ const body = {
838
+ entries: result.entries.map(serializeLog),
839
+ hasNextPage: result.hasNextPage
840
+ };
841
+ if (result.nextCursor !== void 0) {
842
+ body.nextCursor = result.nextCursor;
843
+ }
844
+ return { status: 200, body };
815
845
  } catch {
816
846
  return { status: 500, body: { error: "Internal server error" } };
817
847
  }
@@ -831,7 +861,7 @@ function createAuditConsoleEndpoints(api) {
831
861
  if (!log) {
832
862
  return { status: 404, body: { error: "Audit log not found" } };
833
863
  }
834
- return { status: 200, body: log };
864
+ return { status: 200, body: serializeLog(log) };
835
865
  } catch {
836
866
  return { status: 500, body: { error: "Internal server error" } };
837
867
  }
@@ -844,8 +874,11 @@ function createAuditConsoleEndpoints(api) {
844
874
  async handler(request) {
845
875
  try {
846
876
  const options = {};
847
- const since = parseIsoDate(request.query.since);
848
- if (since !== void 0) {
877
+ if (request.query.since !== void 0) {
878
+ const since = parseIsoDate(request.query.since);
879
+ if (since === void 0) {
880
+ return { status: 400, body: { error: "Invalid 'since': must be an ISO-8601 date" } };
881
+ }
849
882
  options.since = since;
850
883
  }
851
884
  const stats = await api.getStats(options);
@@ -881,8 +914,12 @@ function createAuditConsoleEndpoints(api) {
881
914
  if (hasLongQueryParam(request.query)) {
882
915
  return { status: 400, body: { error: "Query parameter exceeds maximum length" } };
883
916
  }
884
- const filters = parseConsoleQueryFilters(request.query);
885
- const data = await api.exportLogs(filters, format);
917
+ const parsed = parseConsoleQueryFilters(request.query);
918
+ if ("error" in parsed) {
919
+ return { status: 400, body: { error: parsed.error } };
920
+ }
921
+ const exportFormat = format;
922
+ const data = await api.exportLogs(parsed.filters, exportFormat);
886
923
  return { status: 200, body: data };
887
924
  } catch {
888
925
  return { status: 500, body: { error: "Internal server error" } };
@@ -895,9 +932,11 @@ function createAuditConsoleEndpoints(api) {
895
932
  requiredPermission: "admin",
896
933
  async handler(request) {
897
934
  try {
898
- const body = request.body;
899
- const beforeValue = body?.before;
900
- if (beforeValue === void 0 || beforeValue === null) {
935
+ if (!isPlainObject(request.body)) {
936
+ return { status: 400, body: { error: "Request body is required" } };
937
+ }
938
+ const beforeValue = request.body.before;
939
+ if (typeof beforeValue !== "string") {
901
940
  return { status: 400, body: { error: "Missing required field: before" } };
902
941
  }
903
942
  const before = new Date(beforeValue);
@@ -905,8 +944,11 @@ function createAuditConsoleEndpoints(api) {
905
944
  return { status: 400, body: { error: "Invalid date for 'before' field" } };
906
945
  }
907
946
  const options = { before };
908
- if (typeof body?.tableName === "string" && body.tableName.length > 0) {
909
- options.tableName = body.tableName;
947
+ if (typeof request.body.tableName === "string" && request.body.tableName.length > 0) {
948
+ if (exceedsMaxLength(request.body.tableName)) {
949
+ return { status: 400, body: { error: "tableName exceeds maximum length" } };
950
+ }
951
+ options.tableName = request.body.tableName;
910
952
  }
911
953
  const result = await api.purgeLogs(options);
912
954
  return { status: 200, body: result };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/context.ts","../src/diff.ts","../src/enrichment-registry.ts","../src/normalize.ts","../src/duration.ts","../src/query-builder.ts","../src/audit-api.ts","../src/console-endpoints.ts","../src/better-audit.ts","../src/audit-log-schema.ts","../src/context-extractor.ts"],"sourcesContent":["export type {\n AuditLog,\n AuditOperation,\n AuditSeverity,\n AuditContext,\n AuditDatabaseAdapter,\n AuditStats,\n BetterAuditConfig,\n CaptureLogInput,\n BetterAuditInstance,\n EnrichmentConfig,\n EnrichmentDescriptionContext,\n BeforeLogHook,\n AfterLogHook,\n ConsoleRegistration,\n} from \"./types.js\";\n\nexport type {\n ResourceFilter,\n TimeFilter,\n AuditQueryFilters,\n AuditQuerySpec,\n AuditQueryResult,\n} from \"./query-types.js\";\n\nexport { betterAudit } from \"./better-audit.js\";\nexport {\n getAuditContext,\n runWithAuditContext,\n mergeAuditContext,\n} from \"./context.js\";\nexport { normalizeInput } from \"./normalize.js\";\nexport { AUDIT_LOG_SCHEMA } from \"./audit-log-schema.js\";\nexport type {\n AuditLogColumnName,\n AuditLogSchema,\n ColumnDefinition,\n ColumnType,\n} from \"./audit-log-schema.js\";\nexport { AuditQueryBuilder } from \"./query-builder.js\";\nexport type { QueryExecutor } from \"./query-builder.js\";\nexport { parseDuration } from \"./duration.js\";\nexport { createAuditApi } from \"./audit-api.js\";\nexport type { AuditApi, ConsoleQueryFilters, ConsoleQueryResult, EnrichmentSummary } from \"./audit-api.js\";\nexport { createAuditConsoleEndpoints } from \"./console-endpoints.js\";\nexport {\n fromBearerToken,\n fromCookie,\n fromHeader,\n handleMiddleware,\n} from \"./context-extractor.js\";\nexport type {\n ValueExtractor,\n ContextExtractor,\n MiddlewareHandlerOptions,\n} from \"./context-extractor.js\";\n","import type { AuditContext } from \"./types.js\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst storage = new AsyncLocalStorage<AuditContext>();\n\n/**\n * Returns the current audit context, or undefined when not inside a request\n * scope (middleware or withContext).\n */\nexport function getAuditContext(): AuditContext | undefined {\n return storage.getStore();\n}\n\n/**\n * Run fn inside a scope where getAuditContext() returns the given context.\n */\nexport function runWithAuditContext<T>(\n context: AuditContext,\n fn: () => T | Promise<T>,\n): Promise<T> {\n return Promise.resolve(storage.run(context, () => fn()));\n}\n\n/**\n * Merge additional context into the current scope and run fn.\n * If no scope exists, a new one is created from the partial context.\n * Properties in override take precedence over the existing context.\n */\nexport function mergeAuditContext<T>(\n override: Partial<AuditContext>,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const current = storage.getStore() ?? {};\n const merged: AuditContext = { ...current, ...override };\n return Promise.resolve(storage.run(merged, () => fn()));\n}\n","/**\n * Compute which fields changed between two row snapshots.\n * Uses JSON.stringify for deep equality — order-sensitive for objects/arrays.\n */\nexport function computeDiff(\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined,\n): { changedFields: string[] } {\n const b = before ?? {};\n const a = after ?? {};\n const allKeys = new Set([...Object.keys(b), ...Object.keys(a)]);\n const changedFields: string[] = [];\n\n for (const key of allKeys) {\n const inBefore = Object.prototype.hasOwnProperty.call(b, key);\n const inAfter = Object.prototype.hasOwnProperty.call(a, key);\n if (!inBefore || !inAfter) {\n changedFields.push(key);\n continue;\n }\n try {\n if (JSON.stringify(b[key]) !== JSON.stringify(a[key])) {\n changedFields.push(key);\n }\n } catch {\n // Non-serializable value (e.g. circular reference) — treat as changed\n changedFields.push(key);\n }\n }\n\n return { changedFields };\n}\n","import type {\n AuditLog,\n AuditOperation,\n AuditSeverity,\n EnrichmentConfig,\n EnrichmentDescriptionContext,\n} from \"./types.js\";\n\n/**\n * Specificity tiers for enrichment resolution (least → most specific):\n *\n * 1. \"*:*\" — global catch-all\n * 2. \"*:OP\" — any table, specific operation\n * 3. \"table:*\" — specific table, any operation\n * 4. \"table:OP\" — exact match\n *\n * A table-scoped rule is more specific than an operation-scoped rule because\n * tables are the primary organizational unit for audit policies. A rule like\n * \"users:*\" (all ops on users) should override \"*:DELETE\" (all deletes) for\n * scalar fields, since the policy author has explicitly targeted that table.\n */\n\nfunction makeKey(table: string, operation: string): string {\n return `${table}:${operation.toUpperCase()}`;\n}\n\nfunction validateRedactInclude(config: EnrichmentConfig, key: string): void {\n if (\n config.redact !== undefined &&\n config.redact.length > 0 &&\n config.include !== undefined &&\n config.include.length > 0\n ) {\n throw new Error(\n `Enrichment for \"${key}\" cannot specify both \"redact\" and \"include\". ` +\n \"Use one or the other.\",\n );\n }\n}\n\n/** Resolved enrichment config after merging all matching tiers. */\nexport interface ResolvedEnrichment {\n label?: string;\n description?: (context: EnrichmentDescriptionContext) => string;\n severity?: AuditSeverity;\n compliance?: string[];\n notify?: boolean;\n redact?: string[];\n include?: string[];\n}\n\nexport class EnrichmentRegistry {\n private readonly entries = new Map<string, EnrichmentConfig[]>();\n\n register(table: string, operation: AuditOperation | \"*\", config: EnrichmentConfig): void {\n const key = makeKey(table, operation);\n validateRedactInclude(config, key);\n\n const existing = this.entries.get(key);\n if (existing !== undefined) {\n existing.push(config);\n } else {\n this.entries.set(key, [config]);\n }\n }\n\n getEntries(): Array<{ table: string; operation: string; configs: EnrichmentConfig[] }> {\n const result: Array<{ table: string; operation: string; configs: EnrichmentConfig[] }> = [];\n for (const [key, configs] of this.entries) {\n const separatorIndex = key.indexOf(\":\");\n const table = key.slice(0, separatorIndex);\n const operation = key.slice(separatorIndex + 1);\n result.push({ table, operation, configs });\n }\n return result;\n }\n\n resolve(\n table: string,\n operation: string,\n ): ResolvedEnrichment | undefined {\n const normalizedOp = operation.toUpperCase();\n\n // Collect configs from all matching tiers in specificity order\n const keysToCheck = [\n makeKey(\"*\", \"*\"),\n makeKey(\"*\", normalizedOp),\n makeKey(table, \"*\"),\n makeKey(table, normalizedOp),\n ];\n\n const allConfigs: EnrichmentConfig[] = [];\n for (const key of keysToCheck) {\n const configs = this.entries.get(key);\n if (configs !== undefined) {\n for (const config of configs) {\n allConfigs.push(config);\n }\n }\n }\n\n if (allConfigs.length === 0) {\n return undefined;\n }\n\n return mergeEnrichmentConfigs(allConfigs, `${table}:${normalizedOp}`);\n }\n}\n\n/**\n * Merge multiple enrichment configs in order (earlier = less specific).\n * - Scalars: last-write-wins (more specific overrides)\n * - Arrays: concatenate & deduplicate\n * - `description` function: last-write-wins\n *\n * Throws if the merged result contains both `redact` and `include`.\n */\nexport function mergeEnrichmentConfigs(\n configs: readonly EnrichmentConfig[],\n contextKey: string,\n): ResolvedEnrichment {\n const result: ResolvedEnrichment = {};\n\n for (const config of configs) {\n if (config.label !== undefined) {\n result.label = config.label;\n }\n if (config.description !== undefined) {\n result.description = config.description;\n }\n if (config.severity !== undefined) {\n result.severity = config.severity;\n }\n if (config.notify !== undefined) {\n result.notify = config.notify;\n }\n\n if (config.compliance !== undefined) {\n if (result.compliance !== undefined) {\n const merged = [...result.compliance, ...config.compliance];\n result.compliance = [...new Set(merged)];\n } else {\n result.compliance = [...config.compliance];\n }\n }\n\n if (config.redact !== undefined) {\n if (result.redact !== undefined) {\n const merged = [...result.redact, ...config.redact];\n result.redact = [...new Set(merged)];\n } else {\n result.redact = [...config.redact];\n }\n }\n\n if (config.include !== undefined) {\n if (result.include !== undefined) {\n const merged = [...result.include, ...config.include];\n result.include = [...new Set(merged)];\n } else {\n result.include = [...config.include];\n }\n }\n }\n\n // Post-merge conflict check: redact + include from different registrations\n if (\n result.redact !== undefined &&\n result.redact.length > 0 &&\n result.include !== undefined &&\n result.include.length > 0\n ) {\n throw new Error(\n `Enrichment merge for \"${contextKey}\" produced both \"redact\" and \"include\". ` +\n \"These are mutually exclusive — fix the conflicting registrations.\",\n );\n }\n\n return result;\n}\n\n/**\n * Remove or filter fields from beforeData, afterData, and diff.changedFields.\n * Operates on the log in place.\n */\nexport function applyFieldRedaction(\n log: AuditLog,\n resolved: ResolvedEnrichment,\n): void {\n const { redact, include } = resolved;\n const removedFields = new Set<string>();\n\n if (redact !== undefined && redact.length > 0) {\n const redactSet = new Set(redact);\n\n if (log.beforeData !== undefined) {\n for (const key of Object.keys(log.beforeData)) {\n if (redactSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.beforeData = filterOutKeys(log.beforeData, redactSet);\n }\n if (log.afterData !== undefined) {\n for (const key of Object.keys(log.afterData)) {\n if (redactSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.afterData = filterOutKeys(log.afterData, redactSet);\n }\n if (log.diff !== undefined) {\n log.diff = {\n changedFields: log.diff.changedFields.filter(\n (field) => !redactSet.has(field),\n ),\n };\n }\n } else if (include !== undefined && include.length > 0) {\n const includeSet = new Set(include);\n\n if (log.beforeData !== undefined) {\n for (const key of Object.keys(log.beforeData)) {\n if (!includeSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.beforeData = keepOnlyKeys(log.beforeData, includeSet);\n }\n if (log.afterData !== undefined) {\n for (const key of Object.keys(log.afterData)) {\n if (!includeSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.afterData = keepOnlyKeys(log.afterData, includeSet);\n }\n if (log.diff !== undefined) {\n log.diff = {\n changedFields: log.diff.changedFields.filter((field) =>\n includeSet.has(field),\n ),\n };\n }\n }\n\n if (removedFields.size > 0) {\n log.redactedFields = [...removedFields].sort();\n }\n}\n\n/**\n * Apply resolved enrichment to an AuditLog.\n * Enrichment values only fill gaps — explicit per-call and context values take precedence.\n *\n * Flow:\n * 1. Redact fields (before description sees data)\n * 2. Call description function with post-redaction context\n * 3. Apply scalar/array enrichment fields\n */\nexport function applyEnrichment(\n log: AuditLog,\n resolved: ResolvedEnrichment,\n): void {\n // Step 1: Redact fields first\n applyFieldRedaction(log, resolved);\n\n // Step 2: Call description with post-redaction data\n if (resolved.description !== undefined && log.description === undefined) {\n try {\n const descriptionContext: EnrichmentDescriptionContext = {\n before: log.beforeData !== undefined ? structuredClone(log.beforeData) : undefined,\n after: log.afterData !== undefined ? structuredClone(log.afterData) : undefined,\n diff: log.diff !== undefined ? structuredClone(log.diff) : undefined,\n actorId: log.actorId,\n metadata: log.metadata !== undefined ? structuredClone(log.metadata) : undefined,\n };\n\n log.description = resolved.description(descriptionContext);\n } catch {\n // Description function or data cloning threw — leave description unset, log still gets written\n }\n }\n\n // Step 3: Apply scalar/array enrichment (only if not already set)\n if (resolved.label !== undefined && log.label === undefined) {\n log.label = resolved.label;\n }\n if (resolved.severity !== undefined && log.severity === undefined) {\n log.severity = resolved.severity;\n }\n if (resolved.notify !== undefined && log.notify === undefined) {\n log.notify = resolved.notify;\n }\n if (resolved.compliance !== undefined) {\n if (log.compliance !== undefined) {\n const merged = [...log.compliance, ...resolved.compliance];\n log.compliance = [...new Set(merged)];\n } else {\n log.compliance = [...resolved.compliance];\n }\n }\n}\n\nfunction filterOutKeys(\n data: Record<string, unknown>,\n keysToRemove: Set<string>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (!keysToRemove.has(key)) {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction keepOnlyKeys(\n data: Record<string, unknown>,\n keysToKeep: Set<string>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (keysToKeep.has(key)) {\n result[key] = value;\n }\n }\n return result;\n}\n","import type { AuditOperation } from \"./types.js\";\n\n/**\n * Normalize before/after data based on the operation type.\n *\n * - **INSERT** → `before` is dropped (only `after` is meaningful)\n * - **DELETE** → `after` is dropped (only `before` is meaningful)\n * - **UPDATE** → both are kept as-is\n */\nexport function normalizeInput(\n operation: AuditOperation,\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined,\n): {\n before: Record<string, unknown> | undefined;\n after: Record<string, unknown> | undefined;\n} {\n switch (operation) {\n case \"INSERT\": {\n return { before: undefined, after };\n }\n case \"DELETE\": {\n return { before, after: undefined };\n }\n case \"UPDATE\": {\n return { before, after };\n }\n }\n}\n","const DURATION_PATTERN = /^(\\d+)([hdwmy])$/;\n\ntype DurationUnit = \"h\" | \"d\" | \"w\" | \"m\" | \"y\";\n\nfunction isValidUnit(raw: string): raw is DurationUnit {\n return raw === \"h\" || raw === \"d\" || raw === \"w\" || raw === \"m\" || raw === \"y\";\n}\n\n/**\n * Parses a duration string (e.g. \"30d\", \"2w\", \"3m\", \"1y\") and returns\n * a Date that is `duration` before `referenceDate`.\n *\n * Supported units: h (hours), d (days), w (weeks), m (months), y (years).\n * Zero values are rejected.\n */\nexport function parseDuration(input: string, referenceDate?: Date): Date {\n const match = DURATION_PATTERN.exec(input);\n if (match === null) {\n throw new Error(\n `Invalid duration \"${input}\". Expected format: <number><unit> where unit is h, d, w, m, or y (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").`,\n );\n }\n\n const value = Number(match[1]);\n const rawUnit = match[2] as string;\n\n if (value === 0) {\n throw new Error(\n `Invalid duration \"${input}\". Value must be greater than zero.`,\n );\n }\n\n if (!isValidUnit(rawUnit)) {\n throw new Error(\n `Invalid duration unit \"${rawUnit}\". Expected h, d, w, m, or y.`,\n );\n }\n\n const result = referenceDate !== undefined ? new Date(referenceDate) : new Date();\n\n if (rawUnit === \"h\") {\n result.setHours(result.getHours() - value);\n } else if (rawUnit === \"d\") {\n result.setDate(result.getDate() - value);\n } else if (rawUnit === \"w\") {\n result.setDate(result.getDate() - value * 7);\n } else if (rawUnit === \"m\") {\n result.setMonth(result.getMonth() - value);\n } else {\n result.setFullYear(result.getFullYear() - value);\n }\n\n return result;\n}\n","import type { AuditOperation, AuditSeverity } from \"./types.js\";\nimport type {\n AuditQueryFilters,\n AuditQueryResult,\n AuditQuerySpec,\n TimeFilter,\n} from \"./query-types.js\";\nimport { parseDuration } from \"./duration.js\";\n\n/** Callback that executes a query spec against the adapter. */\nexport type QueryExecutor = (spec: AuditQuerySpec) => Promise<AuditQueryResult>;\n\nconst DEFAULT_MAX_QUERY_LIMIT = 1000;\nconst MAX_SEARCH_TEXT_LENGTH = 500;\n\n/**\n * Fluent, immutable query builder for audit logs.\n *\n * Each method returns a **new** instance — safe to fork and share.\n * Call `.list()` to execute, or `.toSpec()` to inspect without executing.\n */\nexport class AuditQueryBuilder {\n readonly #executor: QueryExecutor;\n readonly #filters: AuditQueryFilters;\n readonly #limit: number | undefined;\n readonly #cursor: string | undefined;\n readonly #maxLimit: number;\n readonly #sortOrder: \"asc\" | \"desc\" | undefined;\n\n constructor(\n executor: QueryExecutor,\n filters?: AuditQueryFilters,\n limit?: number,\n cursor?: string,\n maxLimit?: number,\n sortOrder?: \"asc\" | \"desc\",\n ) {\n this.#executor = executor;\n this.#filters = filters ?? {};\n this.#limit = limit;\n this.#cursor = cursor;\n this.#maxLimit = maxLimit ?? DEFAULT_MAX_QUERY_LIMIT;\n this.#sortOrder = sortOrder;\n }\n\n /** Filter by table name and optional record ID. Last-write-wins. */\n resource(tableName: string, recordId?: string): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n {\n ...this.#filters,\n resource: recordId !== undefined\n ? { tableName, recordId }\n : { tableName },\n },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Filter by actor IDs (OR semantics, deduplicates). */\n actor(...ids: string[]): AuditQueryBuilder {\n const existing = this.#filters.actorIds ?? [];\n const merged = [...new Set([...existing, ...ids])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, actorIds: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add severity levels to the filter (OR semantics, deduplicates). */\n severity(...levels: AuditSeverity[]): AuditQueryBuilder {\n const existing = this.#filters.severities ?? [];\n const merged = [...new Set([...existing, ...levels])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, severities: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add compliance tags to the filter (AND semantics, deduplicates). */\n compliance(...tags: string[]): AuditQueryBuilder {\n const existing = this.#filters.compliance ?? [];\n const merged = [...new Set([...existing, ...tags])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, compliance: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /**\n * Filter entries created after a point in time.\n *\n * Accepts a `Date` or a duration string (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").\n * Duration strings are eagerly validated but resolved at query time for a fresh \"now\".\n * Last-write-wins.\n */\n since(value: Date | string): AuditQueryBuilder {\n const filter = this.#parseTimeFilter(value);\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, since: filter },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /**\n * Filter entries created before a point in time.\n *\n * Accepts a `Date` or a duration string (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").\n * Duration strings are eagerly validated but resolved at query time for a fresh \"now\".\n * Last-write-wins.\n */\n until(value: Date | string): AuditQueryBuilder {\n const filter = this.#parseTimeFilter(value);\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, until: filter },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Full-text search filter. Last-write-wins. Max 500 characters. */\n search(text: string): AuditQueryBuilder {\n if (text.length > MAX_SEARCH_TEXT_LENGTH) {\n throw new Error(\n `searchText must be at most ${MAX_SEARCH_TEXT_LENGTH} characters, got ${text.length}`,\n );\n }\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, searchText: text },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add operation types to the filter (OR semantics, deduplicates). */\n operation(...ops: AuditOperation[]): AuditQueryBuilder {\n const existing = this.#filters.operations ?? [];\n const merged = [...new Set([...existing, ...ops])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, operations: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set maximum number of entries to return. Must be > 0 and <= maxLimit. */\n limit(n: number): AuditQueryBuilder {\n if (n <= 0) {\n throw new Error(`limit must be greater than 0, got ${n}`);\n }\n if (n > this.#maxLimit) {\n throw new Error(\n `limit ${n} exceeds maximum allowed limit of ${this.#maxLimit}`,\n );\n }\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n n,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set the pagination cursor for fetching the next page. */\n after(cursor: string): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n this.#limit,\n cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set the sort direction for results. */\n order(direction: \"asc\" | \"desc\"): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n direction,\n );\n }\n\n /** Returns the query specification without executing. Useful for tests and debugging. */\n toSpec(): AuditQuerySpec {\n const filters: AuditQueryFilters = { ...this.#filters };\n // Deep-clone arrays so the returned spec is fully detached from builder internals\n if (filters.severities !== undefined) {\n filters.severities = [...filters.severities];\n }\n if (filters.operations !== undefined) {\n filters.operations = [...filters.operations];\n }\n if (filters.compliance !== undefined) {\n filters.compliance = [...filters.compliance];\n }\n if (filters.actorIds !== undefined) {\n filters.actorIds = [...filters.actorIds];\n }\n const spec: AuditQuerySpec = { filters };\n const effectiveLimit = this.#limit ?? this.#maxLimit;\n spec.limit = effectiveLimit;\n if (this.#cursor !== undefined) {\n spec.cursor = this.#cursor;\n }\n if (this.#sortOrder !== undefined) {\n spec.sortOrder = this.#sortOrder;\n }\n return spec;\n }\n\n /** Execute the query against the adapter. */\n list(): Promise<AuditQueryResult> {\n return this.#executor(this.toSpec());\n }\n\n #parseTimeFilter(value: Date | string): TimeFilter {\n if (value instanceof Date) {\n return { date: value };\n }\n // Eagerly validate — throws if format is invalid\n parseDuration(value);\n return { duration: value };\n }\n}\n","import type { AuditDatabaseAdapter, AuditLog, AuditStats, AuditSeverity } from \"./types.js\";\nimport type { AuditQueryResult, AuditQuerySpec, TimeFilter } from \"./query-types.js\";\nimport type { EnrichmentRegistry } from \"./enrichment-registry.js\";\n\n/** Flat console-friendly query filters. Single-value fields translated to multi-value internal filters. */\nexport interface ConsoleQueryFilters {\n tableName?: string;\n operation?: string;\n actorId?: string;\n severity?: string;\n compliance?: string;\n since?: Date;\n until?: Date;\n search?: string;\n limit?: number;\n cursor?: string;\n}\n\n/** Serializable summary of an enrichment config (function fields stripped). */\nexport interface EnrichmentSummary {\n table: string;\n operation: string;\n label?: string;\n severity?: AuditSeverity;\n compliance?: string[];\n notify?: boolean;\n redact?: string[];\n include?: string[];\n}\n\n/** Query result extended with a convenience `hasNextPage` flag. */\nexport interface ConsoleQueryResult extends AuditQueryResult {\n hasNextPage: boolean;\n}\n\n/**\n * High-level API consumed by console endpoints.\n *\n * Wraps the low-level `AuditDatabaseAdapter` methods with sensible defaults\n * (e.g. clamping `limit` to the configured maximum).\n */\nexport interface AuditApi {\n /** Query audit log entries with optional flat filters and cursor-based pagination. */\n queryLogs(filters?: ConsoleQueryFilters): Promise<ConsoleQueryResult>;\n /** Retrieve a single audit log entry by its ID. Returns `null` when not found. */\n getLog(id: string): Promise<AuditLog | null>;\n /** Get aggregated audit statistics. Requires adapter.getStats. */\n getStats(options?: { since?: Date }): Promise<AuditStats>;\n /** Get serializable summaries of all registered enrichments. */\n getEnrichments(): EnrichmentSummary[];\n /** Export logs as CSV or JSON string. */\n exportLogs(filters?: ConsoleQueryFilters, format?: \"csv\" | \"json\"): Promise<string>;\n /** Purge audit logs before a given date. Requires adapter.purgeLogs. */\n purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }>;\n}\n\nconst CSV_HEADERS = [\n \"id\",\n \"timestamp\",\n \"tableName\",\n \"operation\",\n \"recordId\",\n \"actorId\",\n \"severity\",\n \"label\",\n \"description\",\n] as const;\n\nfunction escapeCsvField(value: string): string {\n if (value.includes('\"') || value.includes(\",\") || value.includes(\"\\n\") || value.includes(\"\\r\")) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\nfunction logToCsvRow(log: AuditLog): string {\n const fields = [\n log.id,\n log.timestamp instanceof Date ? log.timestamp.toISOString() : String(log.timestamp),\n log.tableName,\n log.operation,\n log.recordId,\n log.actorId ?? \"\",\n log.severity ?? \"\",\n log.label ?? \"\",\n log.description ?? \"\",\n ];\n return fields.map(escapeCsvField).join(\",\");\n}\n\nfunction toTimeFilter(date: Date): TimeFilter {\n return { date };\n}\n\nfunction buildQuerySpec(filters: ConsoleQueryFilters, effectiveLimit: number): AuditQuerySpec {\n const limit = Math.min(filters.limit ?? effectiveLimit, effectiveLimit);\n\n const spec: AuditQuerySpec = {\n filters: {},\n limit,\n };\n\n if (filters.tableName !== undefined) {\n spec.filters.resource = { tableName: filters.tableName };\n }\n if (filters.actorId !== undefined) {\n spec.filters.actorIds = [filters.actorId];\n }\n if (filters.severity !== undefined) {\n spec.filters.severities = [filters.severity as AuditSeverity];\n }\n if (filters.compliance !== undefined) {\n spec.filters.compliance = [filters.compliance];\n }\n if (filters.operation !== undefined) {\n spec.filters.operations = [filters.operation.toUpperCase() as \"INSERT\" | \"UPDATE\" | \"DELETE\"];\n }\n if (filters.since !== undefined) {\n spec.filters.since = toTimeFilter(filters.since);\n }\n if (filters.until !== undefined) {\n spec.filters.until = toTimeFilter(filters.until);\n }\n if (filters.search !== undefined) {\n spec.filters.searchText = filters.search;\n }\n if (filters.cursor !== undefined) {\n spec.cursor = filters.cursor;\n }\n\n return spec;\n}\n\nexport function createAuditApi(\n adapter: AuditDatabaseAdapter,\n registry: EnrichmentRegistry,\n maxQueryLimit?: number,\n): AuditApi {\n const effectiveLimit = maxQueryLimit ?? 1000;\n\n function requireQueryLogs(): NonNullable<AuditDatabaseAdapter[\"queryLogs\"]> {\n if (adapter.queryLogs === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n return adapter.queryLogs;\n }\n\n function requireGetLogById(): NonNullable<AuditDatabaseAdapter[\"getLogById\"]> {\n if (adapter.getLogById === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements getLogById(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n return adapter.getLogById;\n }\n\n function requireGetStats(): NonNullable<AuditDatabaseAdapter[\"getStats\"]> {\n if (adapter.getStats === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements getStats(). \" +\n \"Check that your ORM adapter supports statistics.\",\n );\n }\n return adapter.getStats;\n }\n\n function requirePurgeLogs(): NonNullable<AuditDatabaseAdapter[\"purgeLogs\"]> {\n if (adapter.purgeLogs === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements purgeLogs(). \" +\n \"Check that your ORM adapter supports log purging.\",\n );\n }\n return adapter.purgeLogs;\n }\n\n async function queryLogs(filters?: ConsoleQueryFilters): Promise<ConsoleQueryResult> {\n const queryFn = requireQueryLogs();\n const spec = buildQuerySpec(filters ?? {}, effectiveLimit);\n const result = await queryFn(spec);\n return {\n entries: result.entries,\n ...(result.nextCursor !== undefined && { nextCursor: result.nextCursor }),\n hasNextPage: result.nextCursor !== undefined,\n };\n }\n\n async function getLog(id: string): Promise<AuditLog | null> {\n const getLogFn = requireGetLogById();\n return getLogFn(id);\n }\n\n async function getStats(options?: { since?: Date }): Promise<AuditStats> {\n const getStatsFn = requireGetStats();\n return getStatsFn(options);\n }\n\n function getEnrichments(): EnrichmentSummary[] {\n const entries = registry.getEntries();\n const summaries: EnrichmentSummary[] = [];\n\n for (const entry of entries) {\n for (const config of entry.configs) {\n const summary: EnrichmentSummary = {\n table: entry.table,\n operation: entry.operation,\n };\n if (config.label !== undefined) {\n summary.label = config.label;\n }\n if (config.severity !== undefined) {\n summary.severity = config.severity;\n }\n if (config.compliance !== undefined) {\n summary.compliance = config.compliance;\n }\n if (config.notify !== undefined) {\n summary.notify = config.notify;\n }\n if (config.redact !== undefined) {\n summary.redact = config.redact;\n }\n if (config.include !== undefined) {\n summary.include = config.include;\n }\n summaries.push(summary);\n }\n }\n\n return summaries;\n }\n\n async function exportLogs(filters?: ConsoleQueryFilters, format?: \"csv\" | \"json\"): Promise<string> {\n const exportFormat = format ?? \"json\";\n const exportFilters: ConsoleQueryFilters = { ...filters, limit: effectiveLimit };\n const result = await queryLogs(exportFilters);\n\n if (exportFormat === \"json\") {\n return JSON.stringify(result.entries);\n }\n\n const rows = [CSV_HEADERS.join(\",\")];\n for (const log of result.entries) {\n rows.push(logToCsvRow(log));\n }\n return rows.join(\"\\n\");\n }\n\n async function purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }> {\n const purgeFn = requirePurgeLogs();\n return purgeFn(options);\n }\n\n return { queryLogs, getLog, getStats, getEnrichments, exportLogs, purgeLogs };\n}\n","import type { ConsoleProductEndpoint, ConsoleProductRequest } from \"@usebetterdev/console-contract\";\nimport type { AuditApi, ConsoleQueryFilters } from \"./audit-api.js\";\n\nconst MAX_PARAM_LENGTH = 1000;\n\nfunction exceedsMaxLength(value: string | undefined): boolean {\n return value !== undefined && value.length > MAX_PARAM_LENGTH;\n}\n\nfunction parsePositiveInt(value: string | undefined, max: number): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return undefined;\n }\n return Math.min(Math.floor(parsed), max);\n}\n\nfunction parseIsoDate(value: string | undefined): Date | undefined {\n if (value === undefined) {\n return undefined;\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return undefined;\n }\n return date;\n}\n\nfunction parseConsoleQueryFilters(query: Record<string, string>): ConsoleQueryFilters {\n const filters: ConsoleQueryFilters = {};\n\n const limit = parsePositiveInt(query.limit, 1000);\n if (limit !== undefined) {\n filters.limit = limit;\n }\n if (query.tableName !== undefined) {\n filters.tableName = query.tableName;\n }\n if (query.operation !== undefined) {\n filters.operation = query.operation;\n }\n if (query.actorId !== undefined) {\n filters.actorId = query.actorId;\n }\n if (query.severity !== undefined) {\n filters.severity = query.severity;\n }\n if (query.compliance !== undefined) {\n filters.compliance = query.compliance;\n }\n if (query.search !== undefined) {\n filters.search = query.search;\n }\n if (query.cursor !== undefined) {\n filters.cursor = query.cursor;\n }\n\n const since = parseIsoDate(query.since);\n if (since !== undefined) {\n filters.since = since;\n }\n const until = parseIsoDate(query.until);\n if (until !== undefined) {\n filters.until = until;\n }\n\n return filters;\n}\n\nfunction hasLongQueryParam(query: Record<string, string>): boolean {\n return (\n exceedsMaxLength(query.tableName) ||\n exceedsMaxLength(query.actorId) ||\n exceedsMaxLength(query.cursor) ||\n exceedsMaxLength(query.operation) ||\n exceedsMaxLength(query.severity) ||\n exceedsMaxLength(query.compliance) ||\n exceedsMaxLength(query.search)\n );\n}\n\nexport function createAuditConsoleEndpoints(\n api: AuditApi,\n): ConsoleProductEndpoint[] {\n return [\n {\n method: \"GET\",\n path: \"/logs\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n if (hasLongQueryParam(request.query)) {\n return { status: 400, body: { error: \"Query parameter exceeds maximum length\" } };\n }\n const filters = parseConsoleQueryFilters(request.query);\n const result = await api.queryLogs(filters);\n return { status: 200, body: result };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/logs/:id\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const id = request.params.id?.trim();\n if (!id) {\n return { status: 400, body: { error: \"Missing log id\" } };\n }\n const log = await api.getLog(id);\n if (!log) {\n return { status: 404, body: { error: \"Audit log not found\" } };\n }\n return { status: 200, body: log };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/stats\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const options: { since?: Date } = {};\n const since = parseIsoDate(request.query.since);\n if (since !== undefined) {\n options.since = since;\n }\n const stats = await api.getStats(options);\n return { status: 200, body: stats };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/enrichments\",\n requiredPermission: \"read\",\n async handler() {\n try {\n const enrichments = api.getEnrichments();\n return { status: 200, body: enrichments };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/export\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const format = request.query.format;\n if (format !== undefined && format !== \"csv\" && format !== \"json\") {\n return { status: 400, body: { error: \"Invalid format. Must be 'csv' or 'json'\" } };\n }\n if (hasLongQueryParam(request.query)) {\n return { status: 400, body: { error: \"Query parameter exceeds maximum length\" } };\n }\n const filters = parseConsoleQueryFilters(request.query);\n const data = await api.exportLogs(filters, format as \"csv\" | \"json\" | undefined);\n return { status: 200, body: data };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"DELETE\",\n path: \"/logs\",\n requiredPermission: \"admin\",\n async handler(request: ConsoleProductRequest) {\n try {\n const body = request.body as Record<string, unknown> | undefined;\n const beforeValue = body?.before;\n if (beforeValue === undefined || beforeValue === null) {\n return { status: 400, body: { error: \"Missing required field: before\" } };\n }\n const before = new Date(beforeValue as string);\n if (Number.isNaN(before.getTime())) {\n return { status: 400, body: { error: \"Invalid date for 'before' field\" } };\n }\n const options: { before: Date; tableName?: string } = { before };\n if (typeof body?.tableName === \"string\" && body.tableName.length > 0) {\n options.tableName = body.tableName;\n }\n const result = await api.purgeLogs(options);\n return { status: 200, body: result };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n ];\n}\n","import type {\n AfterLogHook,\n AuditContext,\n AuditLog,\n AuditOperation,\n BeforeLogHook,\n BetterAuditConfig,\n BetterAuditInstance,\n CaptureLogInput,\n EnrichmentConfig,\n} from \"./types.js\";\nimport { getAuditContext, runWithAuditContext } from \"./context.js\";\nimport { computeDiff } from \"./diff.js\";\nimport { EnrichmentRegistry, applyEnrichment } from \"./enrichment-registry.js\";\nimport { normalizeInput } from \"./normalize.js\";\nimport { AuditQueryBuilder } from \"./query-builder.js\";\nimport { createAuditApi } from \"./audit-api.js\";\nimport { createAuditConsoleEndpoints } from \"./console-endpoints.js\";\n\nfunction withContext<T>(\n context: AuditContext,\n fn: () => Promise<T>,\n): Promise<T> {\n return runWithAuditContext(context, fn);\n}\n\nexport function betterAudit(config: BetterAuditConfig): BetterAuditInstance {\n const { database } = config;\n const auditTables = new Set(config.auditTables);\n const registry = new EnrichmentRegistry();\n const beforeLogHooks: BeforeLogHook[] = config.beforeLog !== undefined ? [...config.beforeLog] : [];\n const afterLogHooks: AfterLogHook[] = config.afterLog !== undefined ? [...config.afterLog] : [];\n\n function enrich(\n table: string,\n operation: AuditOperation | \"*\",\n enrichmentConfig: EnrichmentConfig,\n ): void {\n if (table !== \"*\" && !auditTables.has(table)) {\n throw new Error(\n `Cannot register enrichment for table \"${table}\": it is not in auditTables. ` +\n `Registered tables: ${[...auditTables].join(\", \")}`,\n );\n }\n\n registry.register(table, operation, enrichmentConfig);\n }\n\n function onBeforeLog(hook: BeforeLogHook): () => void {\n beforeLogHooks.push(hook);\n return () => {\n const index = beforeLogHooks.indexOf(hook);\n if (index !== -1) {\n beforeLogHooks.splice(index, 1);\n }\n };\n }\n\n function onAfterLog(hook: AfterLogHook): () => void {\n afterLogHooks.push(hook);\n return () => {\n const index = afterLogHooks.indexOf(hook);\n if (index !== -1) {\n afterLogHooks.splice(index, 1);\n }\n };\n }\n\n async function writeAndRunAfterHooks(log: AuditLog): Promise<void> {\n await database.writeLog(log);\n for (const hook of afterLogHooks) {\n await hook(log);\n }\n }\n\n async function captureLog(input: CaptureLogInput): Promise<void> {\n // 1. Early return if table not in auditTables\n if (!auditTables.has(input.tableName)) {\n return;\n }\n\n if (input.recordId === \"\") {\n throw new Error(\"captureLog requires a non-empty recordId\");\n }\n\n // 2. Normalize input based on operation type\n const normalized = normalizeInput(input.operation, input.before, input.after);\n\n const context = getAuditContext();\n\n // 3. Assemble log (merge input + AuditContext)\n // Conditional spreads satisfy exactOptionalPropertyTypes — properties\n // are either absent or carry a narrowed non-undefined value.\n const actorId = input.actorId ?? context?.actorId;\n const label = input.label ?? context?.label;\n const reason = input.reason ?? context?.reason;\n const compliance = input.compliance ?? context?.compliance;\n const metadata = input.metadata ?? context?.metadata;\n\n const log: AuditLog = {\n id: crypto.randomUUID(),\n timestamp: new Date(),\n tableName: input.tableName,\n operation: input.operation,\n recordId: input.recordId,\n ...(actorId !== undefined && { actorId }),\n ...(label !== undefined && { label }),\n ...(reason !== undefined && { reason }),\n ...(compliance !== undefined && { compliance }),\n ...(metadata !== undefined && { metadata }),\n ...(input.description !== undefined && { description: input.description }),\n ...(input.severity !== undefined && { severity: input.severity }),\n ...(input.notify !== undefined && { notify: input.notify }),\n ...(normalized.before !== undefined && { beforeData: { ...normalized.before } }),\n ...(normalized.after !== undefined && { afterData: { ...normalized.after } }),\n };\n\n // 4. Compute diff only for UPDATE operations\n if (input.operation === \"UPDATE\") {\n const diff = computeDiff(normalized.before, normalized.after);\n if (diff.changedFields.length > 0) {\n log.diff = diff;\n }\n }\n\n // 5. Resolve enrichment → Redact → Describe → Apply scalars\n const resolved = registry.resolve(input.tableName, input.operation);\n if (resolved !== undefined) {\n applyEnrichment(log, resolved);\n }\n\n // 6. Run beforeLog hooks (sequential, may mutate log, errors abort)\n for (const hook of beforeLogHooks) {\n await hook(log);\n }\n\n // 7. Write log (sync or async) + afterLog hooks\n const isAsync = input.asyncWrite ?? config.asyncWrite ?? false;\n if (isAsync) {\n void writeAndRunAfterHooks(log).catch((error: unknown) => {\n if (config.onError !== undefined) {\n config.onError(error);\n } else {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`audit: async write failed for ${log.tableName}/${log.id} — ${message}`);\n }\n });\n } else {\n await writeAndRunAfterHooks(log);\n }\n }\n\n function query(): AuditQueryBuilder {\n if (database.queryLogs === undefined) {\n throw new Error(\n \"audit.query() requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n const queryLogs = database.queryLogs;\n return new AuditQueryBuilder(\n (spec) => queryLogs(spec),\n undefined,\n undefined,\n undefined,\n config.maxQueryLimit,\n );\n }\n\n if (config.console) {\n const api = createAuditApi(database, registry, config.maxQueryLimit);\n const endpoints = createAuditConsoleEndpoints(api);\n config.console.registerProduct({\n id: \"audit\",\n name: \"Better Audit\",\n endpoints,\n });\n }\n\n return { captureLog, query, withContext, enrich, onBeforeLog, onAfterLog };\n}\n","/**\n * Logical schema for the `audit_logs` table.\n *\n * ORM adapters translate this into their own migration format.\n * Core never runs SQL — this is a declarative data structure only.\n */\n\nexport type ColumnType =\n | \"uuid\"\n | \"timestamptz\"\n | \"text\"\n | \"jsonb\"\n | \"boolean\";\n\nexport interface ColumnDefinition {\n type: ColumnType;\n nullable: boolean;\n primaryKey?: boolean;\n defaultExpression?: string;\n indexed?: boolean;\n description: string;\n}\n\n/**\n * Column names in the `audit_logs` table use snake_case.\n * These map to camelCase properties in the `AuditLog` TypeScript type:\n *\n * | Column (snake_case) | AuditLog property (camelCase) |\n * |---------------------|-------------------------------|\n * | table_name | tableName |\n * | record_id | recordId |\n * | actor_id | actorId |\n * | before_data | beforeData |\n * | after_data | afterData |\n * | redacted_fields | redactedFields |\n */\nexport type AuditLogColumnName =\n | \"id\"\n | \"timestamp\"\n | \"table_name\"\n | \"operation\"\n | \"record_id\"\n | \"actor_id\"\n | \"before_data\"\n | \"after_data\"\n | \"diff\"\n | \"label\"\n | \"description\"\n | \"severity\"\n | \"compliance\"\n | \"notify\"\n | \"reason\"\n | \"metadata\"\n | \"redacted_fields\";\n\nexport interface AuditLogSchema {\n tableName: string;\n columns: Record<string, ColumnDefinition>;\n}\n\nexport const AUDIT_LOG_SCHEMA: AuditLogSchema = {\n tableName: \"audit_logs\",\n columns: {\n id: {\n type: \"uuid\",\n nullable: false,\n primaryKey: true,\n defaultExpression: \"gen_random_uuid()\",\n description: \"Unique identifier for the audit log entry\",\n },\n timestamp: {\n type: \"timestamptz\",\n nullable: false,\n defaultExpression: \"now()\",\n indexed: true,\n description: \"When the audited event occurred\",\n },\n table_name: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Name of the table that was modified\",\n },\n operation: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Type of operation: INSERT, UPDATE, or DELETE\",\n },\n record_id: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Primary key of the affected record\",\n },\n actor_id: {\n type: \"text\",\n nullable: true,\n indexed: true,\n description: \"Identifier of the user or system that performed the action\",\n },\n before_data: {\n type: \"jsonb\",\n nullable: true,\n description: \"Row snapshot before the mutation (DELETE and UPDATE only)\",\n },\n after_data: {\n type: \"jsonb\",\n nullable: true,\n description: \"Row snapshot after the mutation (INSERT and UPDATE only)\",\n },\n diff: {\n type: \"jsonb\",\n nullable: true,\n description: \"Changed field names for UPDATE operations\",\n },\n label: {\n type: \"text\",\n nullable: true,\n description: \"Human-readable label for the event\",\n },\n description: {\n type: \"text\",\n nullable: true,\n description: \"Detailed description of what happened\",\n },\n severity: {\n type: \"text\",\n nullable: true,\n description: \"Severity level: low, medium, high, or critical\",\n },\n compliance: {\n type: \"jsonb\",\n nullable: true,\n description: \"Compliance framework tags (e.g. soc2, gdpr, hipaa)\",\n },\n notify: {\n type: \"boolean\",\n nullable: true,\n description: \"Whether this event should trigger notifications\",\n },\n reason: {\n type: \"text\",\n nullable: true,\n description: \"Justification or reason for the action\",\n },\n metadata: {\n type: \"jsonb\",\n nullable: true,\n description: \"Arbitrary key-value metadata attached to the event\",\n },\n redacted_fields: {\n type: \"jsonb\",\n nullable: true,\n description: \"Field names that were removed by redaction rules\",\n },\n },\n};\n","import type { AuditContext } from \"./types.js\";\nimport { runWithAuditContext } from \"./context.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Function that extracts a string value from a Web Request.\n * Return undefined if the value cannot be extracted.\n * May be sync or async.\n */\nexport type ValueExtractor = (\n request: Request,\n) => string | undefined | Promise<string | undefined>;\n\n/**\n * Describes how to extract audit identity from an incoming HTTP request.\n * All fields are optional — if omitted, the corresponding context field\n * will be undefined.\n */\nexport interface ContextExtractor {\n /** Extracts the actor identifier (user / service account). */\n actor?: ValueExtractor;\n}\n\n/**\n * Options for `handleMiddleware`.\n */\nexport interface MiddlewareHandlerOptions {\n /** Called when an extractor throws. Defaults to silent fail-open. */\n onError?: (error: unknown) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Built-in extractors\n// ---------------------------------------------------------------------------\n\n/**\n * Decode a JWT payload without verification.\n * Uses only Web-standard APIs (atob) — safe on edge runtimes.\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split(\".\");\n if (parts.length < 2) {\n return null;\n }\n const payload = parts[1];\n if (!payload) {\n return null;\n }\n let base64 = payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (base64.length % 4 !== 0) {\n base64 += \"=\";\n }\n const decoded = atob(base64);\n const parsed: unknown = JSON.parse(decoded);\n if (typeof parsed !== \"object\" || parsed === null) {\n return null;\n }\n return parsed as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Extracts a JWT claim from the `Authorization: Bearer <token>` header.\n *\n * Decodes the token **without** signature verification — signing is the\n * auth layer's responsibility. This is intentional: the audit layer only\n * needs the identity, not proof of authenticity.\n *\n * @param claim - JWT claim name to extract (e.g. `\"sub\"`, `\"tenant_id\"`)\n */\nexport function fromBearerToken(claim: string): ValueExtractor {\n return (request: Request) => {\n const authorization = request.headers.get(\"authorization\");\n if (!authorization) {\n return undefined;\n }\n const parts = authorization.split(\" \");\n if (parts.length !== 2 || parts[0]?.toLowerCase() !== \"bearer\") {\n return undefined;\n }\n const token = parts[1];\n if (!token) {\n return undefined;\n }\n const payload = decodeJwtPayload(token);\n if (!payload) {\n return undefined;\n }\n const value = payload[claim];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n };\n}\n\n/**\n * Extracts a value from a named cookie in the `Cookie` header.\n *\n * The raw cookie value is returned as-is. To resolve it into a user identity,\n * compose with a resolver function (e.g. look up a session in a database).\n *\n * @param cookieName - Name of the cookie to read\n */\nexport function fromCookie(cookieName: string): ValueExtractor {\n return (request: Request) => {\n const cookieHeader = request.headers.get(\"cookie\");\n if (!cookieHeader) {\n return undefined;\n }\n const cookies = cookieHeader.split(\";\");\n for (const cookie of cookies) {\n const separatorIndex = cookie.indexOf(\"=\");\n if (separatorIndex === -1) {\n continue;\n }\n const name = cookie.slice(0, separatorIndex).trim();\n if (name === cookieName) {\n const value = cookie.slice(separatorIndex + 1).trim();\n return value.length > 0 ? value : undefined;\n }\n }\n return undefined;\n };\n}\n\n/**\n * Extracts a value from a custom request header.\n *\n * @param headerName - Header name (case-insensitive per the Web API)\n */\nexport function fromHeader(headerName: string): ValueExtractor {\n return (request: Request) => {\n const value = request.headers.get(headerName);\n if (!value || value.trim().length === 0) {\n return undefined;\n }\n return value.trim();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared middleware handler\n// ---------------------------------------------------------------------------\n\n/**\n * Runs an extractor safely, catching errors and returning undefined on failure.\n */\nasync function safeExtract(\n extractor: ValueExtractor | undefined,\n request: Request,\n onError: ((error: unknown) => void) | undefined,\n): Promise<string | undefined> {\n if (!extractor) {\n return undefined;\n }\n try {\n return await extractor(request);\n } catch (error: unknown) {\n if (onError) {\n onError(error);\n }\n return undefined;\n }\n}\n\n/**\n * Shared middleware handler used by all framework adapters.\n *\n * 1. Extracts actor from the request using the provided extractor\n * 2. Builds an `AuditContext` (undefined if nothing was extracted)\n * 3. Wraps `next()` inside `runWithAuditContext()` if context is available\n * 4. Calls `next()` without context if extraction yields nothing\n *\n * Extraction failures never break the request (fail open).\n */\nexport async function handleMiddleware(\n extractor: ContextExtractor,\n request: Request,\n next: () => Promise<void>,\n options: MiddlewareHandlerOptions = {},\n): Promise<void> {\n const actorId = await safeExtract(extractor.actor, request, options.onError);\n\n if (actorId === undefined) {\n await next();\n return;\n }\n\n const auditContext: AuditContext = { actorId };\n\n await runWithAuditContext(auditContext, () => next());\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/context.ts","../src/diff.ts","../src/enrichment-registry.ts","../src/normalize.ts","../src/duration.ts","../src/query-builder.ts","../src/audit-api.ts","../../../shared/console-utils/src/index.ts","../src/console-endpoints.ts","../src/better-audit.ts","../src/audit-log-schema.ts","../src/context-extractor.ts"],"sourcesContent":["export type {\n AuditLog,\n AuditOperation,\n AuditSeverity,\n AuditContext,\n AuditDatabaseAdapter,\n AuditStats,\n BetterAuditConfig,\n CaptureLogInput,\n BetterAuditInstance,\n EnrichmentConfig,\n EnrichmentDescriptionContext,\n BeforeLogHook,\n AfterLogHook,\n ConsoleRegistration,\n} from \"./types.js\";\n\nexport type {\n ResourceFilter,\n TimeFilter,\n AuditQueryFilters,\n AuditQuerySpec,\n AuditQueryResult,\n} from \"./query-types.js\";\n\nexport { betterAudit } from \"./better-audit.js\";\nexport {\n getAuditContext,\n runWithAuditContext,\n mergeAuditContext,\n} from \"./context.js\";\nexport { normalizeInput } from \"./normalize.js\";\nexport { AUDIT_LOG_SCHEMA } from \"./audit-log-schema.js\";\nexport type {\n AuditLogColumnName,\n AuditLogSchema,\n ColumnDefinition,\n ColumnType,\n} from \"./audit-log-schema.js\";\nexport { AuditQueryBuilder } from \"./query-builder.js\";\nexport type { QueryExecutor } from \"./query-builder.js\";\nexport { parseDuration } from \"./duration.js\";\nexport { createAuditApi } from \"./audit-api.js\";\nexport type { AuditApi, ConsoleQueryFilters, ConsoleQueryResult, EnrichmentSummary } from \"./audit-api.js\";\nexport { createAuditConsoleEndpoints } from \"./console-endpoints.js\";\nexport {\n fromBearerToken,\n fromCookie,\n fromHeader,\n handleMiddleware,\n} from \"./context-extractor.js\";\nexport type {\n ValueExtractor,\n ContextExtractor,\n MiddlewareHandlerOptions,\n} from \"./context-extractor.js\";\n","import type { AuditContext } from \"./types.js\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst storage = new AsyncLocalStorage<AuditContext>();\n\n/**\n * Returns the current audit context, or undefined when not inside a request\n * scope (middleware or withContext).\n */\nexport function getAuditContext(): AuditContext | undefined {\n return storage.getStore();\n}\n\n/**\n * Run fn inside a scope where getAuditContext() returns the given context.\n */\nexport function runWithAuditContext<T>(\n context: AuditContext,\n fn: () => T | Promise<T>,\n): Promise<T> {\n return Promise.resolve(storage.run(context, () => fn()));\n}\n\n/**\n * Merge additional context into the current scope and run fn.\n * If no scope exists, a new one is created from the partial context.\n * Properties in override take precedence over the existing context.\n */\nexport function mergeAuditContext<T>(\n override: Partial<AuditContext>,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const current = storage.getStore() ?? {};\n const merged: AuditContext = { ...current, ...override };\n return Promise.resolve(storage.run(merged, () => fn()));\n}\n","/**\n * Compute which fields changed between two row snapshots.\n * Uses JSON.stringify for deep equality — order-sensitive for objects/arrays.\n */\nexport function computeDiff(\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined,\n): { changedFields: string[] } {\n const b = before ?? {};\n const a = after ?? {};\n const allKeys = new Set([...Object.keys(b), ...Object.keys(a)]);\n const changedFields: string[] = [];\n\n for (const key of allKeys) {\n const inBefore = Object.prototype.hasOwnProperty.call(b, key);\n const inAfter = Object.prototype.hasOwnProperty.call(a, key);\n if (!inBefore || !inAfter) {\n changedFields.push(key);\n continue;\n }\n try {\n if (JSON.stringify(b[key]) !== JSON.stringify(a[key])) {\n changedFields.push(key);\n }\n } catch {\n // Non-serializable value (e.g. circular reference) — treat as changed\n changedFields.push(key);\n }\n }\n\n return { changedFields };\n}\n","import type {\n AuditLog,\n AuditOperation,\n AuditSeverity,\n EnrichmentConfig,\n EnrichmentDescriptionContext,\n} from \"./types.js\";\n\n/**\n * Specificity tiers for enrichment resolution (least → most specific):\n *\n * 1. \"*:*\" — global catch-all\n * 2. \"*:OP\" — any table, specific operation\n * 3. \"table:*\" — specific table, any operation\n * 4. \"table:OP\" — exact match\n *\n * A table-scoped rule is more specific than an operation-scoped rule because\n * tables are the primary organizational unit for audit policies. A rule like\n * \"users:*\" (all ops on users) should override \"*:DELETE\" (all deletes) for\n * scalar fields, since the policy author has explicitly targeted that table.\n */\n\nfunction makeKey(table: string, operation: string): string {\n return `${table}:${operation.toUpperCase()}`;\n}\n\nfunction validateRedactInclude(config: EnrichmentConfig, key: string): void {\n if (\n config.redact !== undefined &&\n config.redact.length > 0 &&\n config.include !== undefined &&\n config.include.length > 0\n ) {\n throw new Error(\n `Enrichment for \"${key}\" cannot specify both \"redact\" and \"include\". ` +\n \"Use one or the other.\",\n );\n }\n}\n\n/** Resolved enrichment config after merging all matching tiers. */\nexport interface ResolvedEnrichment {\n label?: string;\n description?: (context: EnrichmentDescriptionContext) => string;\n severity?: AuditSeverity;\n compliance?: string[];\n notify?: boolean;\n redact?: string[];\n include?: string[];\n}\n\nexport class EnrichmentRegistry {\n private readonly entries = new Map<string, EnrichmentConfig[]>();\n\n register(table: string, operation: AuditOperation | \"*\", config: EnrichmentConfig): void {\n const key = makeKey(table, operation);\n validateRedactInclude(config, key);\n\n const existing = this.entries.get(key);\n if (existing !== undefined) {\n existing.push(config);\n } else {\n this.entries.set(key, [config]);\n }\n }\n\n getEntries(): Array<{ table: string; operation: string; configs: EnrichmentConfig[] }> {\n const result: Array<{ table: string; operation: string; configs: EnrichmentConfig[] }> = [];\n for (const [key, configs] of this.entries) {\n const separatorIndex = key.indexOf(\":\");\n const table = key.slice(0, separatorIndex);\n const operation = key.slice(separatorIndex + 1);\n result.push({ table, operation, configs });\n }\n return result;\n }\n\n resolve(\n table: string,\n operation: string,\n ): ResolvedEnrichment | undefined {\n const normalizedOp = operation.toUpperCase();\n\n // Collect configs from all matching tiers in specificity order\n const keysToCheck = [\n makeKey(\"*\", \"*\"),\n makeKey(\"*\", normalizedOp),\n makeKey(table, \"*\"),\n makeKey(table, normalizedOp),\n ];\n\n const allConfigs: EnrichmentConfig[] = [];\n for (const key of keysToCheck) {\n const configs = this.entries.get(key);\n if (configs !== undefined) {\n for (const config of configs) {\n allConfigs.push(config);\n }\n }\n }\n\n if (allConfigs.length === 0) {\n return undefined;\n }\n\n return mergeEnrichmentConfigs(allConfigs, `${table}:${normalizedOp}`);\n }\n}\n\n/**\n * Merge multiple enrichment configs in order (earlier = less specific).\n * - Scalars: last-write-wins (more specific overrides)\n * - Arrays: concatenate & deduplicate\n * - `description` function: last-write-wins\n *\n * Throws if the merged result contains both `redact` and `include`.\n */\nexport function mergeEnrichmentConfigs(\n configs: readonly EnrichmentConfig[],\n contextKey: string,\n): ResolvedEnrichment {\n const result: ResolvedEnrichment = {};\n\n for (const config of configs) {\n if (config.label !== undefined) {\n result.label = config.label;\n }\n if (config.description !== undefined) {\n result.description = config.description;\n }\n if (config.severity !== undefined) {\n result.severity = config.severity;\n }\n if (config.notify !== undefined) {\n result.notify = config.notify;\n }\n\n if (config.compliance !== undefined) {\n if (result.compliance !== undefined) {\n const merged = [...result.compliance, ...config.compliance];\n result.compliance = [...new Set(merged)];\n } else {\n result.compliance = [...config.compliance];\n }\n }\n\n if (config.redact !== undefined) {\n if (result.redact !== undefined) {\n const merged = [...result.redact, ...config.redact];\n result.redact = [...new Set(merged)];\n } else {\n result.redact = [...config.redact];\n }\n }\n\n if (config.include !== undefined) {\n if (result.include !== undefined) {\n const merged = [...result.include, ...config.include];\n result.include = [...new Set(merged)];\n } else {\n result.include = [...config.include];\n }\n }\n }\n\n // Post-merge conflict check: redact + include from different registrations\n if (\n result.redact !== undefined &&\n result.redact.length > 0 &&\n result.include !== undefined &&\n result.include.length > 0\n ) {\n throw new Error(\n `Enrichment merge for \"${contextKey}\" produced both \"redact\" and \"include\". ` +\n \"These are mutually exclusive — fix the conflicting registrations.\",\n );\n }\n\n return result;\n}\n\n/**\n * Remove or filter fields from beforeData, afterData, and diff.changedFields.\n * Operates on the log in place.\n */\nexport function applyFieldRedaction(\n log: AuditLog,\n resolved: ResolvedEnrichment,\n): void {\n const { redact, include } = resolved;\n const removedFields = new Set<string>();\n\n if (redact !== undefined && redact.length > 0) {\n const redactSet = new Set(redact);\n\n if (log.beforeData !== undefined) {\n for (const key of Object.keys(log.beforeData)) {\n if (redactSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.beforeData = filterOutKeys(log.beforeData, redactSet);\n }\n if (log.afterData !== undefined) {\n for (const key of Object.keys(log.afterData)) {\n if (redactSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.afterData = filterOutKeys(log.afterData, redactSet);\n }\n if (log.diff !== undefined) {\n log.diff = {\n changedFields: log.diff.changedFields.filter(\n (field) => !redactSet.has(field),\n ),\n };\n }\n } else if (include !== undefined && include.length > 0) {\n const includeSet = new Set(include);\n\n if (log.beforeData !== undefined) {\n for (const key of Object.keys(log.beforeData)) {\n if (!includeSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.beforeData = keepOnlyKeys(log.beforeData, includeSet);\n }\n if (log.afterData !== undefined) {\n for (const key of Object.keys(log.afterData)) {\n if (!includeSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.afterData = keepOnlyKeys(log.afterData, includeSet);\n }\n if (log.diff !== undefined) {\n log.diff = {\n changedFields: log.diff.changedFields.filter((field) =>\n includeSet.has(field),\n ),\n };\n }\n }\n\n if (removedFields.size > 0) {\n log.redactedFields = [...removedFields].sort();\n }\n}\n\n/**\n * Apply resolved enrichment to an AuditLog.\n * Enrichment values only fill gaps — explicit per-call and context values take precedence.\n *\n * Flow:\n * 1. Redact fields (before description sees data)\n * 2. Call description function with post-redaction context\n * 3. Apply scalar/array enrichment fields\n */\nexport function applyEnrichment(\n log: AuditLog,\n resolved: ResolvedEnrichment,\n): void {\n // Step 1: Redact fields first\n applyFieldRedaction(log, resolved);\n\n // Step 2: Call description with post-redaction data\n if (resolved.description !== undefined && log.description === undefined) {\n try {\n const descriptionContext: EnrichmentDescriptionContext = {\n before: log.beforeData !== undefined ? structuredClone(log.beforeData) : undefined,\n after: log.afterData !== undefined ? structuredClone(log.afterData) : undefined,\n diff: log.diff !== undefined ? structuredClone(log.diff) : undefined,\n actorId: log.actorId,\n metadata: log.metadata !== undefined ? structuredClone(log.metadata) : undefined,\n };\n\n log.description = resolved.description(descriptionContext);\n } catch {\n // Description function or data cloning threw — leave description unset, log still gets written\n }\n }\n\n // Step 3: Apply scalar/array enrichment (only if not already set)\n if (resolved.label !== undefined && log.label === undefined) {\n log.label = resolved.label;\n }\n if (resolved.severity !== undefined && log.severity === undefined) {\n log.severity = resolved.severity;\n }\n if (resolved.notify !== undefined && log.notify === undefined) {\n log.notify = resolved.notify;\n }\n if (resolved.compliance !== undefined) {\n if (log.compliance !== undefined) {\n const merged = [...log.compliance, ...resolved.compliance];\n log.compliance = [...new Set(merged)];\n } else {\n log.compliance = [...resolved.compliance];\n }\n }\n}\n\nfunction filterOutKeys(\n data: Record<string, unknown>,\n keysToRemove: Set<string>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (!keysToRemove.has(key)) {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction keepOnlyKeys(\n data: Record<string, unknown>,\n keysToKeep: Set<string>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (keysToKeep.has(key)) {\n result[key] = value;\n }\n }\n return result;\n}\n","import type { AuditOperation } from \"./types.js\";\n\n/**\n * Normalize before/after data based on the operation type.\n *\n * - **INSERT** → `before` is dropped (only `after` is meaningful)\n * - **DELETE** → `after` is dropped (only `before` is meaningful)\n * - **UPDATE** → both are kept as-is\n */\nexport function normalizeInput(\n operation: AuditOperation,\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined,\n): {\n before: Record<string, unknown> | undefined;\n after: Record<string, unknown> | undefined;\n} {\n switch (operation) {\n case \"INSERT\": {\n return { before: undefined, after };\n }\n case \"DELETE\": {\n return { before, after: undefined };\n }\n case \"UPDATE\": {\n return { before, after };\n }\n }\n}\n","const DURATION_PATTERN = /^(\\d+)([hdwmy])$/;\n\ntype DurationUnit = \"h\" | \"d\" | \"w\" | \"m\" | \"y\";\n\nfunction isValidUnit(raw: string): raw is DurationUnit {\n return raw === \"h\" || raw === \"d\" || raw === \"w\" || raw === \"m\" || raw === \"y\";\n}\n\n/**\n * Parses a duration string (e.g. \"30d\", \"2w\", \"3m\", \"1y\") and returns\n * a Date that is `duration` before `referenceDate`.\n *\n * Supported units: h (hours), d (days), w (weeks), m (months), y (years).\n * Zero values are rejected.\n */\nexport function parseDuration(input: string, referenceDate?: Date): Date {\n const match = DURATION_PATTERN.exec(input);\n if (match === null) {\n throw new Error(\n `Invalid duration \"${input}\". Expected format: <number><unit> where unit is h, d, w, m, or y (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").`,\n );\n }\n\n const value = Number(match[1]);\n const rawUnit = match[2] as string;\n\n if (value === 0) {\n throw new Error(\n `Invalid duration \"${input}\". Value must be greater than zero.`,\n );\n }\n\n if (!isValidUnit(rawUnit)) {\n throw new Error(\n `Invalid duration unit \"${rawUnit}\". Expected h, d, w, m, or y.`,\n );\n }\n\n const result = referenceDate !== undefined ? new Date(referenceDate) : new Date();\n\n if (rawUnit === \"h\") {\n result.setHours(result.getHours() - value);\n } else if (rawUnit === \"d\") {\n result.setDate(result.getDate() - value);\n } else if (rawUnit === \"w\") {\n result.setDate(result.getDate() - value * 7);\n } else if (rawUnit === \"m\") {\n result.setMonth(result.getMonth() - value);\n } else {\n result.setFullYear(result.getFullYear() - value);\n }\n\n return result;\n}\n","import type { AuditOperation, AuditSeverity } from \"./types.js\";\nimport type {\n AuditQueryFilters,\n AuditQueryResult,\n AuditQuerySpec,\n TimeFilter,\n} from \"./query-types.js\";\nimport { parseDuration } from \"./duration.js\";\n\n/** Callback that executes a query spec against the adapter. */\nexport type QueryExecutor = (spec: AuditQuerySpec) => Promise<AuditQueryResult>;\n\nconst DEFAULT_MAX_QUERY_LIMIT = 1000;\nconst MAX_SEARCH_TEXT_LENGTH = 500;\n\n/**\n * Fluent, immutable query builder for audit logs.\n *\n * Each method returns a **new** instance — safe to fork and share.\n * Call `.list()` to execute, or `.toSpec()` to inspect without executing.\n */\nexport class AuditQueryBuilder {\n readonly #executor: QueryExecutor;\n readonly #filters: AuditQueryFilters;\n readonly #limit: number | undefined;\n readonly #cursor: string | undefined;\n readonly #maxLimit: number;\n readonly #sortOrder: \"asc\" | \"desc\" | undefined;\n\n constructor(\n executor: QueryExecutor,\n filters?: AuditQueryFilters,\n limit?: number,\n cursor?: string,\n maxLimit?: number,\n sortOrder?: \"asc\" | \"desc\",\n ) {\n this.#executor = executor;\n this.#filters = filters ?? {};\n this.#limit = limit;\n this.#cursor = cursor;\n this.#maxLimit = maxLimit ?? DEFAULT_MAX_QUERY_LIMIT;\n this.#sortOrder = sortOrder;\n }\n\n /** Filter by table name and optional record ID. Last-write-wins. */\n resource(tableName: string, recordId?: string): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n {\n ...this.#filters,\n resource: recordId !== undefined\n ? { tableName, recordId }\n : { tableName },\n },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Filter by actor IDs (OR semantics, deduplicates). */\n actor(...ids: string[]): AuditQueryBuilder {\n const existing = this.#filters.actorIds ?? [];\n const merged = [...new Set([...existing, ...ids])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, actorIds: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add severity levels to the filter (OR semantics, deduplicates). */\n severity(...levels: AuditSeverity[]): AuditQueryBuilder {\n const existing = this.#filters.severities ?? [];\n const merged = [...new Set([...existing, ...levels])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, severities: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add compliance tags to the filter (AND semantics, deduplicates). */\n compliance(...tags: string[]): AuditQueryBuilder {\n const existing = this.#filters.compliance ?? [];\n const merged = [...new Set([...existing, ...tags])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, compliance: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /**\n * Filter entries created after a point in time.\n *\n * Accepts a `Date` or a duration string (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").\n * Duration strings are eagerly validated but resolved at query time for a fresh \"now\".\n * Last-write-wins.\n */\n since(value: Date | string): AuditQueryBuilder {\n const filter = this.#parseTimeFilter(value);\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, since: filter },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /**\n * Filter entries created before a point in time.\n *\n * Accepts a `Date` or a duration string (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").\n * Duration strings are eagerly validated but resolved at query time for a fresh \"now\".\n * Last-write-wins.\n */\n until(value: Date | string): AuditQueryBuilder {\n const filter = this.#parseTimeFilter(value);\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, until: filter },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Full-text search filter. Last-write-wins. Max 500 characters. */\n search(text: string): AuditQueryBuilder {\n if (text.length > MAX_SEARCH_TEXT_LENGTH) {\n throw new Error(\n `searchText must be at most ${MAX_SEARCH_TEXT_LENGTH} characters, got ${text.length}`,\n );\n }\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, searchText: text },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add operation types to the filter (OR semantics, deduplicates). */\n operation(...ops: AuditOperation[]): AuditQueryBuilder {\n const existing = this.#filters.operations ?? [];\n const merged = [...new Set([...existing, ...ops])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, operations: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set maximum number of entries to return. Must be > 0 and <= maxLimit. */\n limit(n: number): AuditQueryBuilder {\n if (n <= 0) {\n throw new Error(`limit must be greater than 0, got ${n}`);\n }\n if (n > this.#maxLimit) {\n throw new Error(\n `limit ${n} exceeds maximum allowed limit of ${this.#maxLimit}`,\n );\n }\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n n,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set the pagination cursor for fetching the next page. */\n after(cursor: string): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n this.#limit,\n cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set the sort direction for results. */\n order(direction: \"asc\" | \"desc\"): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n direction,\n );\n }\n\n /** Returns the query specification without executing. Useful for tests and debugging. */\n toSpec(): AuditQuerySpec {\n const filters: AuditQueryFilters = { ...this.#filters };\n // Deep-clone arrays so the returned spec is fully detached from builder internals\n if (filters.severities !== undefined) {\n filters.severities = [...filters.severities];\n }\n if (filters.operations !== undefined) {\n filters.operations = [...filters.operations];\n }\n if (filters.compliance !== undefined) {\n filters.compliance = [...filters.compliance];\n }\n if (filters.actorIds !== undefined) {\n filters.actorIds = [...filters.actorIds];\n }\n const spec: AuditQuerySpec = { filters };\n const effectiveLimit = this.#limit ?? this.#maxLimit;\n spec.limit = effectiveLimit;\n if (this.#cursor !== undefined) {\n spec.cursor = this.#cursor;\n }\n if (this.#sortOrder !== undefined) {\n spec.sortOrder = this.#sortOrder;\n }\n return spec;\n }\n\n /** Execute the query against the adapter. */\n list(): Promise<AuditQueryResult> {\n return this.#executor(this.toSpec());\n }\n\n #parseTimeFilter(value: Date | string): TimeFilter {\n if (value instanceof Date) {\n return { date: value };\n }\n // Eagerly validate — throws if format is invalid\n parseDuration(value);\n return { duration: value };\n }\n}\n","import type { AuditDatabaseAdapter, AuditLog, AuditStats, AuditSeverity } from \"./types.js\";\nimport type { AuditQueryResult, AuditQuerySpec, TimeFilter } from \"./query-types.js\";\nimport type { EnrichmentRegistry } from \"./enrichment-registry.js\";\n\n/** Flat console-friendly query filters. Single-value fields translated to multi-value internal filters. */\nexport interface ConsoleQueryFilters {\n tableName?: string;\n operation?: string;\n actorId?: string;\n severity?: string;\n compliance?: string;\n since?: Date;\n until?: Date;\n search?: string;\n limit?: number;\n cursor?: string;\n}\n\n/** Serializable summary of an enrichment config (function fields stripped). */\nexport interface EnrichmentSummary {\n table: string;\n operation: string;\n label?: string;\n severity?: AuditSeverity;\n compliance?: string[];\n notify?: boolean;\n redact?: string[];\n include?: string[];\n}\n\n/** Query result extended with a convenience `hasNextPage` flag. */\nexport interface ConsoleQueryResult extends AuditQueryResult {\n hasNextPage: boolean;\n}\n\n/**\n * High-level API consumed by console endpoints.\n *\n * Wraps the low-level `AuditDatabaseAdapter` methods with sensible defaults\n * (e.g. clamping `limit` to the configured maximum).\n */\nexport interface AuditApi {\n /** Query audit log entries with optional flat filters and cursor-based pagination. */\n queryLogs(filters?: ConsoleQueryFilters): Promise<ConsoleQueryResult>;\n /** Retrieve a single audit log entry by its ID. Returns `null` when not found. */\n getLog(id: string): Promise<AuditLog | null>;\n /** Get aggregated audit statistics. Requires adapter.getStats. */\n getStats(options?: { since?: Date }): Promise<AuditStats>;\n /** Get serializable summaries of all registered enrichments. */\n getEnrichments(): EnrichmentSummary[];\n /** Export logs as CSV or JSON string. */\n exportLogs(filters?: ConsoleQueryFilters, format?: \"csv\" | \"json\"): Promise<string>;\n /** Purge audit logs before a given date. Requires adapter.purgeLogs. */\n purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }>;\n}\n\nconst CSV_HEADERS = [\n \"id\",\n \"timestamp\",\n \"tableName\",\n \"operation\",\n \"recordId\",\n \"actorId\",\n \"severity\",\n \"label\",\n \"description\",\n] as const;\n\nfunction escapeCsvField(value: string): string {\n if (value.includes('\"') || value.includes(\",\") || value.includes(\"\\n\") || value.includes(\"\\r\")) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\nfunction logToCsvRow(log: AuditLog): string {\n const fields = [\n log.id,\n log.timestamp instanceof Date ? log.timestamp.toISOString() : String(log.timestamp),\n log.tableName,\n log.operation,\n log.recordId,\n log.actorId ?? \"\",\n log.severity ?? \"\",\n log.label ?? \"\",\n log.description ?? \"\",\n ];\n return fields.map(escapeCsvField).join(\",\");\n}\n\nfunction toTimeFilter(date: Date): TimeFilter {\n return { date };\n}\n\nfunction buildQuerySpec(filters: ConsoleQueryFilters, effectiveLimit: number): AuditQuerySpec {\n const limit = Math.min(filters.limit ?? effectiveLimit, effectiveLimit);\n\n const spec: AuditQuerySpec = {\n filters: {},\n limit,\n };\n\n if (filters.tableName !== undefined) {\n spec.filters.resource = { tableName: filters.tableName };\n }\n if (filters.actorId !== undefined) {\n spec.filters.actorIds = [filters.actorId];\n }\n if (filters.severity !== undefined) {\n spec.filters.severities = [filters.severity as AuditSeverity];\n }\n if (filters.compliance !== undefined) {\n spec.filters.compliance = [filters.compliance];\n }\n if (filters.operation !== undefined) {\n spec.filters.operations = [filters.operation.toUpperCase() as \"INSERT\" | \"UPDATE\" | \"DELETE\"];\n }\n if (filters.since !== undefined) {\n spec.filters.since = toTimeFilter(filters.since);\n }\n if (filters.until !== undefined) {\n spec.filters.until = toTimeFilter(filters.until);\n }\n if (filters.search !== undefined) {\n spec.filters.searchText = filters.search;\n }\n if (filters.cursor !== undefined) {\n spec.cursor = filters.cursor;\n }\n\n return spec;\n}\n\nexport function createAuditApi(\n adapter: AuditDatabaseAdapter,\n registry: EnrichmentRegistry,\n maxQueryLimit?: number,\n): AuditApi {\n const effectiveLimit = maxQueryLimit ?? 1000;\n\n function requireQueryLogs(): NonNullable<AuditDatabaseAdapter[\"queryLogs\"]> {\n if (adapter.queryLogs === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n return adapter.queryLogs;\n }\n\n function requireGetLogById(): NonNullable<AuditDatabaseAdapter[\"getLogById\"]> {\n if (adapter.getLogById === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements getLogById(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n return adapter.getLogById;\n }\n\n function requireGetStats(): NonNullable<AuditDatabaseAdapter[\"getStats\"]> {\n if (adapter.getStats === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements getStats(). \" +\n \"Check that your ORM adapter supports statistics.\",\n );\n }\n return adapter.getStats;\n }\n\n function requirePurgeLogs(): NonNullable<AuditDatabaseAdapter[\"purgeLogs\"]> {\n if (adapter.purgeLogs === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements purgeLogs(). \" +\n \"Check that your ORM adapter supports log purging.\",\n );\n }\n return adapter.purgeLogs;\n }\n\n async function queryLogs(filters?: ConsoleQueryFilters): Promise<ConsoleQueryResult> {\n const queryFn = requireQueryLogs();\n const spec = buildQuerySpec(filters ?? {}, effectiveLimit);\n const result = await queryFn(spec);\n return {\n entries: result.entries,\n ...(result.nextCursor !== undefined && { nextCursor: result.nextCursor }),\n hasNextPage: result.nextCursor !== undefined,\n };\n }\n\n async function getLog(id: string): Promise<AuditLog | null> {\n const getLogFn = requireGetLogById();\n return getLogFn(id);\n }\n\n async function getStats(options?: { since?: Date }): Promise<AuditStats> {\n const getStatsFn = requireGetStats();\n return getStatsFn(options);\n }\n\n function getEnrichments(): EnrichmentSummary[] {\n const entries = registry.getEntries();\n const summaries: EnrichmentSummary[] = [];\n\n for (const entry of entries) {\n for (const config of entry.configs) {\n const summary: EnrichmentSummary = {\n table: entry.table,\n operation: entry.operation,\n };\n if (config.label !== undefined) {\n summary.label = config.label;\n }\n if (config.severity !== undefined) {\n summary.severity = config.severity;\n }\n if (config.compliance !== undefined) {\n summary.compliance = config.compliance;\n }\n if (config.notify !== undefined) {\n summary.notify = config.notify;\n }\n if (config.redact !== undefined) {\n summary.redact = config.redact;\n }\n if (config.include !== undefined) {\n summary.include = config.include;\n }\n summaries.push(summary);\n }\n }\n\n return summaries;\n }\n\n async function exportLogs(filters?: ConsoleQueryFilters, format?: \"csv\" | \"json\"): Promise<string> {\n const exportFormat = format ?? \"json\";\n const exportFilters: ConsoleQueryFilters = { ...filters, limit: effectiveLimit };\n const result = await queryLogs(exportFilters);\n\n if (exportFormat === \"json\") {\n return JSON.stringify(result.entries);\n }\n\n const rows = [CSV_HEADERS.join(\",\")];\n for (const log of result.entries) {\n rows.push(logToCsvRow(log));\n }\n return rows.join(\"\\n\");\n }\n\n async function purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }> {\n const purgeFn = requirePurgeLogs();\n return purgeFn(options);\n }\n\n return { queryLogs, getLog, getStats, getEnrichments, exportLogs, purgeLogs };\n}\n","/**\n * Shared parse and validation utilities for console endpoint handlers.\n *\n * These are small, pure, zero-dep helpers used by product endpoint handlers\n * (audit, tenant) to validate and parse incoming request data.\n */\n\n/** Type guard that narrows `unknown` to a plain object (not array, not null). */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse a string to an integer within [min, max].\n * Returns `undefined` when `value` is `undefined` (param absent),\n * not a finite number, or below `min`.\n */\nexport function parseBoundedInt(\n value: string | undefined,\n min: number,\n max: number,\n): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed < min) {\n return undefined;\n }\n return Math.min(Math.floor(parsed), max);\n}\n\n/**\n * Parse an ISO-8601 date string.\n * Returns `undefined` when `value` is `undefined` or not a valid date.\n */\nexport function parseIsoDate(value: string | undefined): Date | undefined {\n if (value === undefined) {\n return undefined;\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return undefined;\n }\n return date;\n}\n","import type {\n ConsoleProductEndpoint,\n ConsoleProductRequest,\n ConsoleErrorResponse,\n AuditLogWire,\n AuditLogsResponse,\n AuditStatsResponse,\n AuditEnrichmentsResponse,\n AuditPurgeResponse,\n} from \"@usebetterdev/console-contract\";\nimport {\n isPlainObject,\n parseBoundedInt,\n parseIsoDate,\n} from \"@usebetterdev/console-utils\";\nimport type { AuditApi, ConsoleQueryFilters } from \"./audit-api.js\";\nimport type { AuditLog } from \"./types.js\";\n\nconst MAX_PARAM_LENGTH = 1000;\n\nfunction exceedsMaxLength(value: string | undefined): boolean {\n return value !== undefined && value.length > MAX_PARAM_LENGTH;\n}\n\nfunction hasLongQueryParam(query: Record<string, string>): boolean {\n return (\n exceedsMaxLength(query.tableName) ||\n exceedsMaxLength(query.actorId) ||\n exceedsMaxLength(query.cursor) ||\n exceedsMaxLength(query.operation) ||\n exceedsMaxLength(query.severity) ||\n exceedsMaxLength(query.compliance) ||\n exceedsMaxLength(query.search)\n );\n}\n\nfunction parseConsoleQueryFilters(\n query: Record<string, string>,\n): { filters: ConsoleQueryFilters } | { error: string } {\n const filters: ConsoleQueryFilters = {};\n\n if (query.limit !== undefined) {\n const limit = parseBoundedInt(query.limit, 1, 1000);\n if (limit === undefined) {\n return { error: \"Invalid 'limit': must be a positive integer (max 1000)\" };\n }\n filters.limit = limit;\n }\n if (query.tableName !== undefined) {\n filters.tableName = query.tableName;\n }\n if (query.operation !== undefined) {\n filters.operation = query.operation;\n }\n if (query.actorId !== undefined) {\n filters.actorId = query.actorId;\n }\n if (query.severity !== undefined) {\n filters.severity = query.severity;\n }\n if (query.compliance !== undefined) {\n filters.compliance = query.compliance;\n }\n if (query.search !== undefined) {\n filters.search = query.search;\n }\n if (query.cursor !== undefined) {\n filters.cursor = query.cursor;\n }\n\n if (query.since !== undefined) {\n const since = parseIsoDate(query.since);\n if (since === undefined) {\n return { error: \"Invalid 'since': must be an ISO-8601 date\" };\n }\n filters.since = since;\n }\n if (query.until !== undefined) {\n const until = parseIsoDate(query.until);\n if (until === undefined) {\n return { error: \"Invalid 'until': must be an ISO-8601 date\" };\n }\n filters.until = until;\n }\n\n return { filters };\n}\n\nfunction serializeLog(log: AuditLog): AuditLogWire {\n return {\n ...log,\n timestamp: log.timestamp.toISOString(),\n };\n}\n\nexport function createAuditConsoleEndpoints(\n api: AuditApi,\n): ConsoleProductEndpoint[] {\n return [\n {\n method: \"GET\",\n path: \"/logs\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n if (hasLongQueryParam(request.query)) {\n return { status: 400, body: { error: \"Query parameter exceeds maximum length\" } satisfies ConsoleErrorResponse };\n }\n const parsed = parseConsoleQueryFilters(request.query);\n if (\"error\" in parsed) {\n return { status: 400, body: { error: parsed.error } satisfies ConsoleErrorResponse };\n }\n const result = await api.queryLogs(parsed.filters);\n const body: AuditLogsResponse = {\n entries: result.entries.map(serializeLog),\n hasNextPage: result.hasNextPage,\n };\n if (result.nextCursor !== undefined) {\n body.nextCursor = result.nextCursor;\n }\n return { status: 200, body };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/logs/:id\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const id = request.params.id?.trim();\n if (!id) {\n return { status: 400, body: { error: \"Missing log id\" } satisfies ConsoleErrorResponse };\n }\n const log = await api.getLog(id);\n if (!log) {\n return { status: 404, body: { error: \"Audit log not found\" } satisfies ConsoleErrorResponse };\n }\n return { status: 200, body: serializeLog(log) satisfies AuditLogWire };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/stats\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const options: { since?: Date } = {};\n if (request.query.since !== undefined) {\n const since = parseIsoDate(request.query.since);\n if (since === undefined) {\n return { status: 400, body: { error: \"Invalid 'since': must be an ISO-8601 date\" } satisfies ConsoleErrorResponse };\n }\n options.since = since;\n }\n const stats = await api.getStats(options);\n return { status: 200, body: stats satisfies AuditStatsResponse };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/enrichments\",\n requiredPermission: \"read\",\n async handler() {\n try {\n const enrichments = api.getEnrichments();\n return { status: 200, body: enrichments satisfies AuditEnrichmentsResponse };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/export\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const format = request.query.format;\n if (format !== undefined && format !== \"csv\" && format !== \"json\") {\n return { status: 400, body: { error: \"Invalid format. Must be 'csv' or 'json'\" } satisfies ConsoleErrorResponse };\n }\n if (hasLongQueryParam(request.query)) {\n return { status: 400, body: { error: \"Query parameter exceeds maximum length\" } satisfies ConsoleErrorResponse };\n }\n const parsed = parseConsoleQueryFilters(request.query);\n if (\"error\" in parsed) {\n return { status: 400, body: { error: parsed.error } satisfies ConsoleErrorResponse };\n }\n const exportFormat: \"csv\" | \"json\" | undefined = format;\n const data = await api.exportLogs(parsed.filters, exportFormat);\n return { status: 200, body: data };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"DELETE\",\n path: \"/logs\",\n requiredPermission: \"admin\",\n async handler(request: ConsoleProductRequest) {\n try {\n if (!isPlainObject(request.body)) {\n return { status: 400, body: { error: \"Request body is required\" } satisfies ConsoleErrorResponse };\n }\n const beforeValue = request.body.before;\n if (typeof beforeValue !== \"string\") {\n return { status: 400, body: { error: \"Missing required field: before\" } satisfies ConsoleErrorResponse };\n }\n const before = new Date(beforeValue);\n if (Number.isNaN(before.getTime())) {\n return { status: 400, body: { error: \"Invalid date for 'before' field\" } satisfies ConsoleErrorResponse };\n }\n const options: { before: Date; tableName?: string } = { before };\n if (typeof request.body.tableName === \"string\" && request.body.tableName.length > 0) {\n if (exceedsMaxLength(request.body.tableName)) {\n return { status: 400, body: { error: \"tableName exceeds maximum length\" } satisfies ConsoleErrorResponse };\n }\n options.tableName = request.body.tableName;\n }\n const result = await api.purgeLogs(options);\n return { status: 200, body: result satisfies AuditPurgeResponse };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n ];\n}\n","import type {\n AfterLogHook,\n AuditContext,\n AuditLog,\n AuditOperation,\n BeforeLogHook,\n BetterAuditConfig,\n BetterAuditInstance,\n CaptureLogInput,\n EnrichmentConfig,\n} from \"./types.js\";\nimport { getAuditContext, runWithAuditContext } from \"./context.js\";\nimport { computeDiff } from \"./diff.js\";\nimport { EnrichmentRegistry, applyEnrichment } from \"./enrichment-registry.js\";\nimport { normalizeInput } from \"./normalize.js\";\nimport { AuditQueryBuilder } from \"./query-builder.js\";\nimport { createAuditApi } from \"./audit-api.js\";\nimport { createAuditConsoleEndpoints } from \"./console-endpoints.js\";\n\nfunction withContext<T>(\n context: AuditContext,\n fn: () => Promise<T>,\n): Promise<T> {\n return runWithAuditContext(context, fn);\n}\n\nexport function betterAudit(config: BetterAuditConfig): BetterAuditInstance {\n const { database } = config;\n const auditTables = new Set(config.auditTables);\n const registry = new EnrichmentRegistry();\n const beforeLogHooks: BeforeLogHook[] = config.beforeLog !== undefined ? [...config.beforeLog] : [];\n const afterLogHooks: AfterLogHook[] = config.afterLog !== undefined ? [...config.afterLog] : [];\n\n function enrich(\n table: string,\n operation: AuditOperation | \"*\",\n enrichmentConfig: EnrichmentConfig,\n ): void {\n if (table !== \"*\" && !auditTables.has(table)) {\n throw new Error(\n `Cannot register enrichment for table \"${table}\": it is not in auditTables. ` +\n `Registered tables: ${[...auditTables].join(\", \")}`,\n );\n }\n\n registry.register(table, operation, enrichmentConfig);\n }\n\n function onBeforeLog(hook: BeforeLogHook): () => void {\n beforeLogHooks.push(hook);\n return () => {\n const index = beforeLogHooks.indexOf(hook);\n if (index !== -1) {\n beforeLogHooks.splice(index, 1);\n }\n };\n }\n\n function onAfterLog(hook: AfterLogHook): () => void {\n afterLogHooks.push(hook);\n return () => {\n const index = afterLogHooks.indexOf(hook);\n if (index !== -1) {\n afterLogHooks.splice(index, 1);\n }\n };\n }\n\n async function writeAndRunAfterHooks(log: AuditLog): Promise<void> {\n await database.writeLog(log);\n for (const hook of afterLogHooks) {\n await hook(log);\n }\n }\n\n async function captureLog(input: CaptureLogInput): Promise<void> {\n // 1. Early return if table not in auditTables\n if (!auditTables.has(input.tableName)) {\n return;\n }\n\n if (input.recordId === \"\") {\n throw new Error(\"captureLog requires a non-empty recordId\");\n }\n\n // 2. Normalize input based on operation type\n const normalized = normalizeInput(input.operation, input.before, input.after);\n\n const context = getAuditContext();\n\n // 3. Assemble log (merge input + AuditContext)\n // Conditional spreads satisfy exactOptionalPropertyTypes — properties\n // are either absent or carry a narrowed non-undefined value.\n const actorId = input.actorId ?? context?.actorId;\n const label = input.label ?? context?.label;\n const reason = input.reason ?? context?.reason;\n const compliance = input.compliance ?? context?.compliance;\n const metadata = input.metadata ?? context?.metadata;\n\n const log: AuditLog = {\n id: crypto.randomUUID(),\n timestamp: new Date(),\n tableName: input.tableName,\n operation: input.operation,\n recordId: input.recordId,\n ...(actorId !== undefined && { actorId }),\n ...(label !== undefined && { label }),\n ...(reason !== undefined && { reason }),\n ...(compliance !== undefined && { compliance }),\n ...(metadata !== undefined && { metadata }),\n ...(input.description !== undefined && { description: input.description }),\n ...(input.severity !== undefined && { severity: input.severity }),\n ...(input.notify !== undefined && { notify: input.notify }),\n ...(normalized.before !== undefined && { beforeData: { ...normalized.before } }),\n ...(normalized.after !== undefined && { afterData: { ...normalized.after } }),\n };\n\n // 4. Compute diff only for UPDATE operations\n if (input.operation === \"UPDATE\") {\n const diff = computeDiff(normalized.before, normalized.after);\n if (diff.changedFields.length > 0) {\n log.diff = diff;\n }\n }\n\n // 5. Resolve enrichment → Redact → Describe → Apply scalars\n const resolved = registry.resolve(input.tableName, input.operation);\n if (resolved !== undefined) {\n applyEnrichment(log, resolved);\n }\n\n // 6. Run beforeLog hooks (sequential, may mutate log, errors abort)\n for (const hook of beforeLogHooks) {\n await hook(log);\n }\n\n // 7. Write log (sync or async) + afterLog hooks\n const isAsync = input.asyncWrite ?? config.asyncWrite ?? false;\n if (isAsync) {\n void writeAndRunAfterHooks(log).catch((error: unknown) => {\n if (config.onError !== undefined) {\n config.onError(error);\n } else {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`audit: async write failed for ${log.tableName}/${log.id} — ${message}`);\n }\n });\n } else {\n await writeAndRunAfterHooks(log);\n }\n }\n\n function query(): AuditQueryBuilder {\n if (database.queryLogs === undefined) {\n throw new Error(\n \"audit.query() requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n const queryLogs = database.queryLogs;\n return new AuditQueryBuilder(\n (spec) => queryLogs(spec),\n undefined,\n undefined,\n undefined,\n config.maxQueryLimit,\n );\n }\n\n if (config.console) {\n const api = createAuditApi(database, registry, config.maxQueryLimit);\n const endpoints = createAuditConsoleEndpoints(api);\n config.console.registerProduct({\n id: \"audit\",\n name: \"Better Audit\",\n endpoints,\n });\n }\n\n return { captureLog, query, withContext, enrich, onBeforeLog, onAfterLog };\n}\n","/**\n * Logical schema for the `audit_logs` table.\n *\n * ORM adapters translate this into their own migration format.\n * Core never runs SQL — this is a declarative data structure only.\n */\n\nexport type ColumnType =\n | \"uuid\"\n | \"timestamptz\"\n | \"text\"\n | \"jsonb\"\n | \"boolean\";\n\nexport interface ColumnDefinition {\n type: ColumnType;\n nullable: boolean;\n primaryKey?: boolean;\n defaultExpression?: string;\n indexed?: boolean;\n description: string;\n}\n\n/**\n * Column names in the `audit_logs` table use snake_case.\n * These map to camelCase properties in the `AuditLog` TypeScript type:\n *\n * | Column (snake_case) | AuditLog property (camelCase) |\n * |---------------------|-------------------------------|\n * | table_name | tableName |\n * | record_id | recordId |\n * | actor_id | actorId |\n * | before_data | beforeData |\n * | after_data | afterData |\n * | redacted_fields | redactedFields |\n */\nexport type AuditLogColumnName =\n | \"id\"\n | \"timestamp\"\n | \"table_name\"\n | \"operation\"\n | \"record_id\"\n | \"actor_id\"\n | \"before_data\"\n | \"after_data\"\n | \"diff\"\n | \"label\"\n | \"description\"\n | \"severity\"\n | \"compliance\"\n | \"notify\"\n | \"reason\"\n | \"metadata\"\n | \"redacted_fields\";\n\nexport interface AuditLogSchema {\n tableName: string;\n columns: Record<string, ColumnDefinition>;\n}\n\nexport const AUDIT_LOG_SCHEMA: AuditLogSchema = {\n tableName: \"audit_logs\",\n columns: {\n id: {\n type: \"uuid\",\n nullable: false,\n primaryKey: true,\n defaultExpression: \"gen_random_uuid()\",\n description: \"Unique identifier for the audit log entry\",\n },\n timestamp: {\n type: \"timestamptz\",\n nullable: false,\n defaultExpression: \"now()\",\n indexed: true,\n description: \"When the audited event occurred\",\n },\n table_name: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Name of the table that was modified\",\n },\n operation: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Type of operation: INSERT, UPDATE, or DELETE\",\n },\n record_id: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Primary key of the affected record\",\n },\n actor_id: {\n type: \"text\",\n nullable: true,\n indexed: true,\n description: \"Identifier of the user or system that performed the action\",\n },\n before_data: {\n type: \"jsonb\",\n nullable: true,\n description: \"Row snapshot before the mutation (DELETE and UPDATE only)\",\n },\n after_data: {\n type: \"jsonb\",\n nullable: true,\n description: \"Row snapshot after the mutation (INSERT and UPDATE only)\",\n },\n diff: {\n type: \"jsonb\",\n nullable: true,\n description: \"Changed field names for UPDATE operations\",\n },\n label: {\n type: \"text\",\n nullable: true,\n description: \"Human-readable label for the event\",\n },\n description: {\n type: \"text\",\n nullable: true,\n description: \"Detailed description of what happened\",\n },\n severity: {\n type: \"text\",\n nullable: true,\n description: \"Severity level: low, medium, high, or critical\",\n },\n compliance: {\n type: \"jsonb\",\n nullable: true,\n description: \"Compliance framework tags (e.g. soc2, gdpr, hipaa)\",\n },\n notify: {\n type: \"boolean\",\n nullable: true,\n description: \"Whether this event should trigger notifications\",\n },\n reason: {\n type: \"text\",\n nullable: true,\n description: \"Justification or reason for the action\",\n },\n metadata: {\n type: \"jsonb\",\n nullable: true,\n description: \"Arbitrary key-value metadata attached to the event\",\n },\n redacted_fields: {\n type: \"jsonb\",\n nullable: true,\n description: \"Field names that were removed by redaction rules\",\n },\n },\n};\n","import type { AuditContext } from \"./types.js\";\nimport { runWithAuditContext } from \"./context.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Function that extracts a string value from a Web Request.\n * Return undefined if the value cannot be extracted.\n * May be sync or async.\n */\nexport type ValueExtractor = (\n request: Request,\n) => string | undefined | Promise<string | undefined>;\n\n/**\n * Describes how to extract audit identity from an incoming HTTP request.\n * All fields are optional — if omitted, the corresponding context field\n * will be undefined.\n */\nexport interface ContextExtractor {\n /** Extracts the actor identifier (user / service account). */\n actor?: ValueExtractor;\n}\n\n/**\n * Options for `handleMiddleware`.\n */\nexport interface MiddlewareHandlerOptions {\n /** Called when an extractor throws. Defaults to silent fail-open. */\n onError?: (error: unknown) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Built-in extractors\n// ---------------------------------------------------------------------------\n\n/**\n * Decode a JWT payload without verification.\n * Uses only Web-standard APIs (atob) — safe on edge runtimes.\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split(\".\");\n if (parts.length < 2) {\n return null;\n }\n const payload = parts[1];\n if (!payload) {\n return null;\n }\n let base64 = payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (base64.length % 4 !== 0) {\n base64 += \"=\";\n }\n const decoded = atob(base64);\n const parsed: unknown = JSON.parse(decoded);\n if (typeof parsed !== \"object\" || parsed === null) {\n return null;\n }\n return parsed as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Extracts a JWT claim from the `Authorization: Bearer <token>` header.\n *\n * Decodes the token **without** signature verification — signing is the\n * auth layer's responsibility. This is intentional: the audit layer only\n * needs the identity, not proof of authenticity.\n *\n * @param claim - JWT claim name to extract (e.g. `\"sub\"`, `\"tenant_id\"`)\n */\nexport function fromBearerToken(claim: string): ValueExtractor {\n return (request: Request) => {\n const authorization = request.headers.get(\"authorization\");\n if (!authorization) {\n return undefined;\n }\n const parts = authorization.split(\" \");\n if (parts.length !== 2 || parts[0]?.toLowerCase() !== \"bearer\") {\n return undefined;\n }\n const token = parts[1];\n if (!token) {\n return undefined;\n }\n const payload = decodeJwtPayload(token);\n if (!payload) {\n return undefined;\n }\n const value = payload[claim];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n };\n}\n\n/**\n * Extracts a value from a named cookie in the `Cookie` header.\n *\n * The raw cookie value is returned as-is. To resolve it into a user identity,\n * compose with a resolver function (e.g. look up a session in a database).\n *\n * @param cookieName - Name of the cookie to read\n */\nexport function fromCookie(cookieName: string): ValueExtractor {\n return (request: Request) => {\n const cookieHeader = request.headers.get(\"cookie\");\n if (!cookieHeader) {\n return undefined;\n }\n const cookies = cookieHeader.split(\";\");\n for (const cookie of cookies) {\n const separatorIndex = cookie.indexOf(\"=\");\n if (separatorIndex === -1) {\n continue;\n }\n const name = cookie.slice(0, separatorIndex).trim();\n if (name === cookieName) {\n const value = cookie.slice(separatorIndex + 1).trim();\n return value.length > 0 ? value : undefined;\n }\n }\n return undefined;\n };\n}\n\n/**\n * Extracts a value from a custom request header.\n *\n * @param headerName - Header name (case-insensitive per the Web API)\n */\nexport function fromHeader(headerName: string): ValueExtractor {\n return (request: Request) => {\n const value = request.headers.get(headerName);\n if (!value || value.trim().length === 0) {\n return undefined;\n }\n return value.trim();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared middleware handler\n// ---------------------------------------------------------------------------\n\n/**\n * Runs an extractor safely, catching errors and returning undefined on failure.\n */\nasync function safeExtract(\n extractor: ValueExtractor | undefined,\n request: Request,\n onError: ((error: unknown) => void) | undefined,\n): Promise<string | undefined> {\n if (!extractor) {\n return undefined;\n }\n try {\n return await extractor(request);\n } catch (error: unknown) {\n if (onError) {\n onError(error);\n }\n return undefined;\n }\n}\n\n/**\n * Shared middleware handler used by all framework adapters.\n *\n * 1. Extracts actor from the request using the provided extractor\n * 2. Builds an `AuditContext` (undefined if nothing was extracted)\n * 3. Wraps `next()` inside `runWithAuditContext()` if context is available\n * 4. Calls `next()` without context if extraction yields nothing\n *\n * Extraction failures never break the request (fail open).\n */\nexport async function handleMiddleware(\n extractor: ContextExtractor,\n request: Request,\n next: () => Promise<void>,\n options: MiddlewareHandlerOptions = {},\n): Promise<void> {\n const actorId = await safeExtract(extractor.actor, request, options.onError);\n\n if (actorId === undefined) {\n await next();\n return;\n }\n\n const auditContext: AuditContext = { actorId };\n\n await runWithAuditContext(auditContext, () => next());\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -694,17 +694,16 @@ function createAuditApi(adapter, registry, maxQueryLimit) {
694
694
  return { queryLogs, getLog, getStats, getEnrichments, exportLogs, purgeLogs };
695
695
  }
696
696
 
697
- // src/console-endpoints.ts
698
- var MAX_PARAM_LENGTH = 1e3;
699
- function exceedsMaxLength(value) {
700
- return value !== void 0 && value.length > MAX_PARAM_LENGTH;
697
+ // ../../shared/console-utils/src/index.ts
698
+ function isPlainObject(value) {
699
+ return typeof value === "object" && value !== null && !Array.isArray(value);
701
700
  }
702
- function parsePositiveInt(value, max) {
701
+ function parseBoundedInt(value, min, max) {
703
702
  if (value === void 0) {
704
703
  return void 0;
705
704
  }
706
705
  const parsed = Number(value);
707
- if (!Number.isFinite(parsed) || parsed <= 0) {
706
+ if (!Number.isFinite(parsed) || parsed < min) {
708
707
  return void 0;
709
708
  }
710
709
  return Math.min(Math.floor(parsed), max);
@@ -719,10 +718,22 @@ function parseIsoDate(value) {
719
718
  }
720
719
  return date;
721
720
  }
721
+
722
+ // src/console-endpoints.ts
723
+ var MAX_PARAM_LENGTH = 1e3;
724
+ function exceedsMaxLength(value) {
725
+ return value !== void 0 && value.length > MAX_PARAM_LENGTH;
726
+ }
727
+ function hasLongQueryParam(query) {
728
+ return exceedsMaxLength(query.tableName) || exceedsMaxLength(query.actorId) || exceedsMaxLength(query.cursor) || exceedsMaxLength(query.operation) || exceedsMaxLength(query.severity) || exceedsMaxLength(query.compliance) || exceedsMaxLength(query.search);
729
+ }
722
730
  function parseConsoleQueryFilters(query) {
723
731
  const filters = {};
724
- const limit = parsePositiveInt(query.limit, 1e3);
725
- if (limit !== void 0) {
732
+ if (query.limit !== void 0) {
733
+ const limit = parseBoundedInt(query.limit, 1, 1e3);
734
+ if (limit === void 0) {
735
+ return { error: "Invalid 'limit': must be a positive integer (max 1000)" };
736
+ }
726
737
  filters.limit = limit;
727
738
  }
728
739
  if (query.tableName !== void 0) {
@@ -746,18 +757,27 @@ function parseConsoleQueryFilters(query) {
746
757
  if (query.cursor !== void 0) {
747
758
  filters.cursor = query.cursor;
748
759
  }
749
- const since = parseIsoDate(query.since);
750
- if (since !== void 0) {
760
+ if (query.since !== void 0) {
761
+ const since = parseIsoDate(query.since);
762
+ if (since === void 0) {
763
+ return { error: "Invalid 'since': must be an ISO-8601 date" };
764
+ }
751
765
  filters.since = since;
752
766
  }
753
- const until = parseIsoDate(query.until);
754
- if (until !== void 0) {
767
+ if (query.until !== void 0) {
768
+ const until = parseIsoDate(query.until);
769
+ if (until === void 0) {
770
+ return { error: "Invalid 'until': must be an ISO-8601 date" };
771
+ }
755
772
  filters.until = until;
756
773
  }
757
- return filters;
774
+ return { filters };
758
775
  }
759
- function hasLongQueryParam(query) {
760
- return exceedsMaxLength(query.tableName) || exceedsMaxLength(query.actorId) || exceedsMaxLength(query.cursor) || exceedsMaxLength(query.operation) || exceedsMaxLength(query.severity) || exceedsMaxLength(query.compliance) || exceedsMaxLength(query.search);
776
+ function serializeLog(log) {
777
+ return {
778
+ ...log,
779
+ timestamp: log.timestamp.toISOString()
780
+ };
761
781
  }
762
782
  function createAuditConsoleEndpoints(api) {
763
783
  return [
@@ -770,9 +790,19 @@ function createAuditConsoleEndpoints(api) {
770
790
  if (hasLongQueryParam(request.query)) {
771
791
  return { status: 400, body: { error: "Query parameter exceeds maximum length" } };
772
792
  }
773
- const filters = parseConsoleQueryFilters(request.query);
774
- const result = await api.queryLogs(filters);
775
- return { status: 200, body: result };
793
+ const parsed = parseConsoleQueryFilters(request.query);
794
+ if ("error" in parsed) {
795
+ return { status: 400, body: { error: parsed.error } };
796
+ }
797
+ const result = await api.queryLogs(parsed.filters);
798
+ const body = {
799
+ entries: result.entries.map(serializeLog),
800
+ hasNextPage: result.hasNextPage
801
+ };
802
+ if (result.nextCursor !== void 0) {
803
+ body.nextCursor = result.nextCursor;
804
+ }
805
+ return { status: 200, body };
776
806
  } catch {
777
807
  return { status: 500, body: { error: "Internal server error" } };
778
808
  }
@@ -792,7 +822,7 @@ function createAuditConsoleEndpoints(api) {
792
822
  if (!log) {
793
823
  return { status: 404, body: { error: "Audit log not found" } };
794
824
  }
795
- return { status: 200, body: log };
825
+ return { status: 200, body: serializeLog(log) };
796
826
  } catch {
797
827
  return { status: 500, body: { error: "Internal server error" } };
798
828
  }
@@ -805,8 +835,11 @@ function createAuditConsoleEndpoints(api) {
805
835
  async handler(request) {
806
836
  try {
807
837
  const options = {};
808
- const since = parseIsoDate(request.query.since);
809
- if (since !== void 0) {
838
+ if (request.query.since !== void 0) {
839
+ const since = parseIsoDate(request.query.since);
840
+ if (since === void 0) {
841
+ return { status: 400, body: { error: "Invalid 'since': must be an ISO-8601 date" } };
842
+ }
810
843
  options.since = since;
811
844
  }
812
845
  const stats = await api.getStats(options);
@@ -842,8 +875,12 @@ function createAuditConsoleEndpoints(api) {
842
875
  if (hasLongQueryParam(request.query)) {
843
876
  return { status: 400, body: { error: "Query parameter exceeds maximum length" } };
844
877
  }
845
- const filters = parseConsoleQueryFilters(request.query);
846
- const data = await api.exportLogs(filters, format);
878
+ const parsed = parseConsoleQueryFilters(request.query);
879
+ if ("error" in parsed) {
880
+ return { status: 400, body: { error: parsed.error } };
881
+ }
882
+ const exportFormat = format;
883
+ const data = await api.exportLogs(parsed.filters, exportFormat);
847
884
  return { status: 200, body: data };
848
885
  } catch {
849
886
  return { status: 500, body: { error: "Internal server error" } };
@@ -856,9 +893,11 @@ function createAuditConsoleEndpoints(api) {
856
893
  requiredPermission: "admin",
857
894
  async handler(request) {
858
895
  try {
859
- const body = request.body;
860
- const beforeValue = body?.before;
861
- if (beforeValue === void 0 || beforeValue === null) {
896
+ if (!isPlainObject(request.body)) {
897
+ return { status: 400, body: { error: "Request body is required" } };
898
+ }
899
+ const beforeValue = request.body.before;
900
+ if (typeof beforeValue !== "string") {
862
901
  return { status: 400, body: { error: "Missing required field: before" } };
863
902
  }
864
903
  const before = new Date(beforeValue);
@@ -866,8 +905,11 @@ function createAuditConsoleEndpoints(api) {
866
905
  return { status: 400, body: { error: "Invalid date for 'before' field" } };
867
906
  }
868
907
  const options = { before };
869
- if (typeof body?.tableName === "string" && body.tableName.length > 0) {
870
- options.tableName = body.tableName;
908
+ if (typeof request.body.tableName === "string" && request.body.tableName.length > 0) {
909
+ if (exceedsMaxLength(request.body.tableName)) {
910
+ return { status: 400, body: { error: "tableName exceeds maximum length" } };
911
+ }
912
+ options.tableName = request.body.tableName;
871
913
  }
872
914
  const result = await api.purgeLogs(options);
873
915
  return { status: 200, body: result };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.ts","../src/diff.ts","../src/enrichment-registry.ts","../src/normalize.ts","../src/duration.ts","../src/query-builder.ts","../src/audit-api.ts","../src/console-endpoints.ts","../src/better-audit.ts","../src/audit-log-schema.ts","../src/context-extractor.ts"],"sourcesContent":["import type { AuditContext } from \"./types.js\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst storage = new AsyncLocalStorage<AuditContext>();\n\n/**\n * Returns the current audit context, or undefined when not inside a request\n * scope (middleware or withContext).\n */\nexport function getAuditContext(): AuditContext | undefined {\n return storage.getStore();\n}\n\n/**\n * Run fn inside a scope where getAuditContext() returns the given context.\n */\nexport function runWithAuditContext<T>(\n context: AuditContext,\n fn: () => T | Promise<T>,\n): Promise<T> {\n return Promise.resolve(storage.run(context, () => fn()));\n}\n\n/**\n * Merge additional context into the current scope and run fn.\n * If no scope exists, a new one is created from the partial context.\n * Properties in override take precedence over the existing context.\n */\nexport function mergeAuditContext<T>(\n override: Partial<AuditContext>,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const current = storage.getStore() ?? {};\n const merged: AuditContext = { ...current, ...override };\n return Promise.resolve(storage.run(merged, () => fn()));\n}\n","/**\n * Compute which fields changed between two row snapshots.\n * Uses JSON.stringify for deep equality — order-sensitive for objects/arrays.\n */\nexport function computeDiff(\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined,\n): { changedFields: string[] } {\n const b = before ?? {};\n const a = after ?? {};\n const allKeys = new Set([...Object.keys(b), ...Object.keys(a)]);\n const changedFields: string[] = [];\n\n for (const key of allKeys) {\n const inBefore = Object.prototype.hasOwnProperty.call(b, key);\n const inAfter = Object.prototype.hasOwnProperty.call(a, key);\n if (!inBefore || !inAfter) {\n changedFields.push(key);\n continue;\n }\n try {\n if (JSON.stringify(b[key]) !== JSON.stringify(a[key])) {\n changedFields.push(key);\n }\n } catch {\n // Non-serializable value (e.g. circular reference) — treat as changed\n changedFields.push(key);\n }\n }\n\n return { changedFields };\n}\n","import type {\n AuditLog,\n AuditOperation,\n AuditSeverity,\n EnrichmentConfig,\n EnrichmentDescriptionContext,\n} from \"./types.js\";\n\n/**\n * Specificity tiers for enrichment resolution (least → most specific):\n *\n * 1. \"*:*\" — global catch-all\n * 2. \"*:OP\" — any table, specific operation\n * 3. \"table:*\" — specific table, any operation\n * 4. \"table:OP\" — exact match\n *\n * A table-scoped rule is more specific than an operation-scoped rule because\n * tables are the primary organizational unit for audit policies. A rule like\n * \"users:*\" (all ops on users) should override \"*:DELETE\" (all deletes) for\n * scalar fields, since the policy author has explicitly targeted that table.\n */\n\nfunction makeKey(table: string, operation: string): string {\n return `${table}:${operation.toUpperCase()}`;\n}\n\nfunction validateRedactInclude(config: EnrichmentConfig, key: string): void {\n if (\n config.redact !== undefined &&\n config.redact.length > 0 &&\n config.include !== undefined &&\n config.include.length > 0\n ) {\n throw new Error(\n `Enrichment for \"${key}\" cannot specify both \"redact\" and \"include\". ` +\n \"Use one or the other.\",\n );\n }\n}\n\n/** Resolved enrichment config after merging all matching tiers. */\nexport interface ResolvedEnrichment {\n label?: string;\n description?: (context: EnrichmentDescriptionContext) => string;\n severity?: AuditSeverity;\n compliance?: string[];\n notify?: boolean;\n redact?: string[];\n include?: string[];\n}\n\nexport class EnrichmentRegistry {\n private readonly entries = new Map<string, EnrichmentConfig[]>();\n\n register(table: string, operation: AuditOperation | \"*\", config: EnrichmentConfig): void {\n const key = makeKey(table, operation);\n validateRedactInclude(config, key);\n\n const existing = this.entries.get(key);\n if (existing !== undefined) {\n existing.push(config);\n } else {\n this.entries.set(key, [config]);\n }\n }\n\n getEntries(): Array<{ table: string; operation: string; configs: EnrichmentConfig[] }> {\n const result: Array<{ table: string; operation: string; configs: EnrichmentConfig[] }> = [];\n for (const [key, configs] of this.entries) {\n const separatorIndex = key.indexOf(\":\");\n const table = key.slice(0, separatorIndex);\n const operation = key.slice(separatorIndex + 1);\n result.push({ table, operation, configs });\n }\n return result;\n }\n\n resolve(\n table: string,\n operation: string,\n ): ResolvedEnrichment | undefined {\n const normalizedOp = operation.toUpperCase();\n\n // Collect configs from all matching tiers in specificity order\n const keysToCheck = [\n makeKey(\"*\", \"*\"),\n makeKey(\"*\", normalizedOp),\n makeKey(table, \"*\"),\n makeKey(table, normalizedOp),\n ];\n\n const allConfigs: EnrichmentConfig[] = [];\n for (const key of keysToCheck) {\n const configs = this.entries.get(key);\n if (configs !== undefined) {\n for (const config of configs) {\n allConfigs.push(config);\n }\n }\n }\n\n if (allConfigs.length === 0) {\n return undefined;\n }\n\n return mergeEnrichmentConfigs(allConfigs, `${table}:${normalizedOp}`);\n }\n}\n\n/**\n * Merge multiple enrichment configs in order (earlier = less specific).\n * - Scalars: last-write-wins (more specific overrides)\n * - Arrays: concatenate & deduplicate\n * - `description` function: last-write-wins\n *\n * Throws if the merged result contains both `redact` and `include`.\n */\nexport function mergeEnrichmentConfigs(\n configs: readonly EnrichmentConfig[],\n contextKey: string,\n): ResolvedEnrichment {\n const result: ResolvedEnrichment = {};\n\n for (const config of configs) {\n if (config.label !== undefined) {\n result.label = config.label;\n }\n if (config.description !== undefined) {\n result.description = config.description;\n }\n if (config.severity !== undefined) {\n result.severity = config.severity;\n }\n if (config.notify !== undefined) {\n result.notify = config.notify;\n }\n\n if (config.compliance !== undefined) {\n if (result.compliance !== undefined) {\n const merged = [...result.compliance, ...config.compliance];\n result.compliance = [...new Set(merged)];\n } else {\n result.compliance = [...config.compliance];\n }\n }\n\n if (config.redact !== undefined) {\n if (result.redact !== undefined) {\n const merged = [...result.redact, ...config.redact];\n result.redact = [...new Set(merged)];\n } else {\n result.redact = [...config.redact];\n }\n }\n\n if (config.include !== undefined) {\n if (result.include !== undefined) {\n const merged = [...result.include, ...config.include];\n result.include = [...new Set(merged)];\n } else {\n result.include = [...config.include];\n }\n }\n }\n\n // Post-merge conflict check: redact + include from different registrations\n if (\n result.redact !== undefined &&\n result.redact.length > 0 &&\n result.include !== undefined &&\n result.include.length > 0\n ) {\n throw new Error(\n `Enrichment merge for \"${contextKey}\" produced both \"redact\" and \"include\". ` +\n \"These are mutually exclusive — fix the conflicting registrations.\",\n );\n }\n\n return result;\n}\n\n/**\n * Remove or filter fields from beforeData, afterData, and diff.changedFields.\n * Operates on the log in place.\n */\nexport function applyFieldRedaction(\n log: AuditLog,\n resolved: ResolvedEnrichment,\n): void {\n const { redact, include } = resolved;\n const removedFields = new Set<string>();\n\n if (redact !== undefined && redact.length > 0) {\n const redactSet = new Set(redact);\n\n if (log.beforeData !== undefined) {\n for (const key of Object.keys(log.beforeData)) {\n if (redactSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.beforeData = filterOutKeys(log.beforeData, redactSet);\n }\n if (log.afterData !== undefined) {\n for (const key of Object.keys(log.afterData)) {\n if (redactSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.afterData = filterOutKeys(log.afterData, redactSet);\n }\n if (log.diff !== undefined) {\n log.diff = {\n changedFields: log.diff.changedFields.filter(\n (field) => !redactSet.has(field),\n ),\n };\n }\n } else if (include !== undefined && include.length > 0) {\n const includeSet = new Set(include);\n\n if (log.beforeData !== undefined) {\n for (const key of Object.keys(log.beforeData)) {\n if (!includeSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.beforeData = keepOnlyKeys(log.beforeData, includeSet);\n }\n if (log.afterData !== undefined) {\n for (const key of Object.keys(log.afterData)) {\n if (!includeSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.afterData = keepOnlyKeys(log.afterData, includeSet);\n }\n if (log.diff !== undefined) {\n log.diff = {\n changedFields: log.diff.changedFields.filter((field) =>\n includeSet.has(field),\n ),\n };\n }\n }\n\n if (removedFields.size > 0) {\n log.redactedFields = [...removedFields].sort();\n }\n}\n\n/**\n * Apply resolved enrichment to an AuditLog.\n * Enrichment values only fill gaps — explicit per-call and context values take precedence.\n *\n * Flow:\n * 1. Redact fields (before description sees data)\n * 2. Call description function with post-redaction context\n * 3. Apply scalar/array enrichment fields\n */\nexport function applyEnrichment(\n log: AuditLog,\n resolved: ResolvedEnrichment,\n): void {\n // Step 1: Redact fields first\n applyFieldRedaction(log, resolved);\n\n // Step 2: Call description with post-redaction data\n if (resolved.description !== undefined && log.description === undefined) {\n try {\n const descriptionContext: EnrichmentDescriptionContext = {\n before: log.beforeData !== undefined ? structuredClone(log.beforeData) : undefined,\n after: log.afterData !== undefined ? structuredClone(log.afterData) : undefined,\n diff: log.diff !== undefined ? structuredClone(log.diff) : undefined,\n actorId: log.actorId,\n metadata: log.metadata !== undefined ? structuredClone(log.metadata) : undefined,\n };\n\n log.description = resolved.description(descriptionContext);\n } catch {\n // Description function or data cloning threw — leave description unset, log still gets written\n }\n }\n\n // Step 3: Apply scalar/array enrichment (only if not already set)\n if (resolved.label !== undefined && log.label === undefined) {\n log.label = resolved.label;\n }\n if (resolved.severity !== undefined && log.severity === undefined) {\n log.severity = resolved.severity;\n }\n if (resolved.notify !== undefined && log.notify === undefined) {\n log.notify = resolved.notify;\n }\n if (resolved.compliance !== undefined) {\n if (log.compliance !== undefined) {\n const merged = [...log.compliance, ...resolved.compliance];\n log.compliance = [...new Set(merged)];\n } else {\n log.compliance = [...resolved.compliance];\n }\n }\n}\n\nfunction filterOutKeys(\n data: Record<string, unknown>,\n keysToRemove: Set<string>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (!keysToRemove.has(key)) {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction keepOnlyKeys(\n data: Record<string, unknown>,\n keysToKeep: Set<string>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (keysToKeep.has(key)) {\n result[key] = value;\n }\n }\n return result;\n}\n","import type { AuditOperation } from \"./types.js\";\n\n/**\n * Normalize before/after data based on the operation type.\n *\n * - **INSERT** → `before` is dropped (only `after` is meaningful)\n * - **DELETE** → `after` is dropped (only `before` is meaningful)\n * - **UPDATE** → both are kept as-is\n */\nexport function normalizeInput(\n operation: AuditOperation,\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined,\n): {\n before: Record<string, unknown> | undefined;\n after: Record<string, unknown> | undefined;\n} {\n switch (operation) {\n case \"INSERT\": {\n return { before: undefined, after };\n }\n case \"DELETE\": {\n return { before, after: undefined };\n }\n case \"UPDATE\": {\n return { before, after };\n }\n }\n}\n","const DURATION_PATTERN = /^(\\d+)([hdwmy])$/;\n\ntype DurationUnit = \"h\" | \"d\" | \"w\" | \"m\" | \"y\";\n\nfunction isValidUnit(raw: string): raw is DurationUnit {\n return raw === \"h\" || raw === \"d\" || raw === \"w\" || raw === \"m\" || raw === \"y\";\n}\n\n/**\n * Parses a duration string (e.g. \"30d\", \"2w\", \"3m\", \"1y\") and returns\n * a Date that is `duration` before `referenceDate`.\n *\n * Supported units: h (hours), d (days), w (weeks), m (months), y (years).\n * Zero values are rejected.\n */\nexport function parseDuration(input: string, referenceDate?: Date): Date {\n const match = DURATION_PATTERN.exec(input);\n if (match === null) {\n throw new Error(\n `Invalid duration \"${input}\". Expected format: <number><unit> where unit is h, d, w, m, or y (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").`,\n );\n }\n\n const value = Number(match[1]);\n const rawUnit = match[2] as string;\n\n if (value === 0) {\n throw new Error(\n `Invalid duration \"${input}\". Value must be greater than zero.`,\n );\n }\n\n if (!isValidUnit(rawUnit)) {\n throw new Error(\n `Invalid duration unit \"${rawUnit}\". Expected h, d, w, m, or y.`,\n );\n }\n\n const result = referenceDate !== undefined ? new Date(referenceDate) : new Date();\n\n if (rawUnit === \"h\") {\n result.setHours(result.getHours() - value);\n } else if (rawUnit === \"d\") {\n result.setDate(result.getDate() - value);\n } else if (rawUnit === \"w\") {\n result.setDate(result.getDate() - value * 7);\n } else if (rawUnit === \"m\") {\n result.setMonth(result.getMonth() - value);\n } else {\n result.setFullYear(result.getFullYear() - value);\n }\n\n return result;\n}\n","import type { AuditOperation, AuditSeverity } from \"./types.js\";\nimport type {\n AuditQueryFilters,\n AuditQueryResult,\n AuditQuerySpec,\n TimeFilter,\n} from \"./query-types.js\";\nimport { parseDuration } from \"./duration.js\";\n\n/** Callback that executes a query spec against the adapter. */\nexport type QueryExecutor = (spec: AuditQuerySpec) => Promise<AuditQueryResult>;\n\nconst DEFAULT_MAX_QUERY_LIMIT = 1000;\nconst MAX_SEARCH_TEXT_LENGTH = 500;\n\n/**\n * Fluent, immutable query builder for audit logs.\n *\n * Each method returns a **new** instance — safe to fork and share.\n * Call `.list()` to execute, or `.toSpec()` to inspect without executing.\n */\nexport class AuditQueryBuilder {\n readonly #executor: QueryExecutor;\n readonly #filters: AuditQueryFilters;\n readonly #limit: number | undefined;\n readonly #cursor: string | undefined;\n readonly #maxLimit: number;\n readonly #sortOrder: \"asc\" | \"desc\" | undefined;\n\n constructor(\n executor: QueryExecutor,\n filters?: AuditQueryFilters,\n limit?: number,\n cursor?: string,\n maxLimit?: number,\n sortOrder?: \"asc\" | \"desc\",\n ) {\n this.#executor = executor;\n this.#filters = filters ?? {};\n this.#limit = limit;\n this.#cursor = cursor;\n this.#maxLimit = maxLimit ?? DEFAULT_MAX_QUERY_LIMIT;\n this.#sortOrder = sortOrder;\n }\n\n /** Filter by table name and optional record ID. Last-write-wins. */\n resource(tableName: string, recordId?: string): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n {\n ...this.#filters,\n resource: recordId !== undefined\n ? { tableName, recordId }\n : { tableName },\n },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Filter by actor IDs (OR semantics, deduplicates). */\n actor(...ids: string[]): AuditQueryBuilder {\n const existing = this.#filters.actorIds ?? [];\n const merged = [...new Set([...existing, ...ids])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, actorIds: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add severity levels to the filter (OR semantics, deduplicates). */\n severity(...levels: AuditSeverity[]): AuditQueryBuilder {\n const existing = this.#filters.severities ?? [];\n const merged = [...new Set([...existing, ...levels])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, severities: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add compliance tags to the filter (AND semantics, deduplicates). */\n compliance(...tags: string[]): AuditQueryBuilder {\n const existing = this.#filters.compliance ?? [];\n const merged = [...new Set([...existing, ...tags])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, compliance: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /**\n * Filter entries created after a point in time.\n *\n * Accepts a `Date` or a duration string (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").\n * Duration strings are eagerly validated but resolved at query time for a fresh \"now\".\n * Last-write-wins.\n */\n since(value: Date | string): AuditQueryBuilder {\n const filter = this.#parseTimeFilter(value);\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, since: filter },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /**\n * Filter entries created before a point in time.\n *\n * Accepts a `Date` or a duration string (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").\n * Duration strings are eagerly validated but resolved at query time for a fresh \"now\".\n * Last-write-wins.\n */\n until(value: Date | string): AuditQueryBuilder {\n const filter = this.#parseTimeFilter(value);\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, until: filter },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Full-text search filter. Last-write-wins. Max 500 characters. */\n search(text: string): AuditQueryBuilder {\n if (text.length > MAX_SEARCH_TEXT_LENGTH) {\n throw new Error(\n `searchText must be at most ${MAX_SEARCH_TEXT_LENGTH} characters, got ${text.length}`,\n );\n }\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, searchText: text },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add operation types to the filter (OR semantics, deduplicates). */\n operation(...ops: AuditOperation[]): AuditQueryBuilder {\n const existing = this.#filters.operations ?? [];\n const merged = [...new Set([...existing, ...ops])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, operations: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set maximum number of entries to return. Must be > 0 and <= maxLimit. */\n limit(n: number): AuditQueryBuilder {\n if (n <= 0) {\n throw new Error(`limit must be greater than 0, got ${n}`);\n }\n if (n > this.#maxLimit) {\n throw new Error(\n `limit ${n} exceeds maximum allowed limit of ${this.#maxLimit}`,\n );\n }\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n n,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set the pagination cursor for fetching the next page. */\n after(cursor: string): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n this.#limit,\n cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set the sort direction for results. */\n order(direction: \"asc\" | \"desc\"): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n direction,\n );\n }\n\n /** Returns the query specification without executing. Useful for tests and debugging. */\n toSpec(): AuditQuerySpec {\n const filters: AuditQueryFilters = { ...this.#filters };\n // Deep-clone arrays so the returned spec is fully detached from builder internals\n if (filters.severities !== undefined) {\n filters.severities = [...filters.severities];\n }\n if (filters.operations !== undefined) {\n filters.operations = [...filters.operations];\n }\n if (filters.compliance !== undefined) {\n filters.compliance = [...filters.compliance];\n }\n if (filters.actorIds !== undefined) {\n filters.actorIds = [...filters.actorIds];\n }\n const spec: AuditQuerySpec = { filters };\n const effectiveLimit = this.#limit ?? this.#maxLimit;\n spec.limit = effectiveLimit;\n if (this.#cursor !== undefined) {\n spec.cursor = this.#cursor;\n }\n if (this.#sortOrder !== undefined) {\n spec.sortOrder = this.#sortOrder;\n }\n return spec;\n }\n\n /** Execute the query against the adapter. */\n list(): Promise<AuditQueryResult> {\n return this.#executor(this.toSpec());\n }\n\n #parseTimeFilter(value: Date | string): TimeFilter {\n if (value instanceof Date) {\n return { date: value };\n }\n // Eagerly validate — throws if format is invalid\n parseDuration(value);\n return { duration: value };\n }\n}\n","import type { AuditDatabaseAdapter, AuditLog, AuditStats, AuditSeverity } from \"./types.js\";\nimport type { AuditQueryResult, AuditQuerySpec, TimeFilter } from \"./query-types.js\";\nimport type { EnrichmentRegistry } from \"./enrichment-registry.js\";\n\n/** Flat console-friendly query filters. Single-value fields translated to multi-value internal filters. */\nexport interface ConsoleQueryFilters {\n tableName?: string;\n operation?: string;\n actorId?: string;\n severity?: string;\n compliance?: string;\n since?: Date;\n until?: Date;\n search?: string;\n limit?: number;\n cursor?: string;\n}\n\n/** Serializable summary of an enrichment config (function fields stripped). */\nexport interface EnrichmentSummary {\n table: string;\n operation: string;\n label?: string;\n severity?: AuditSeverity;\n compliance?: string[];\n notify?: boolean;\n redact?: string[];\n include?: string[];\n}\n\n/** Query result extended with a convenience `hasNextPage` flag. */\nexport interface ConsoleQueryResult extends AuditQueryResult {\n hasNextPage: boolean;\n}\n\n/**\n * High-level API consumed by console endpoints.\n *\n * Wraps the low-level `AuditDatabaseAdapter` methods with sensible defaults\n * (e.g. clamping `limit` to the configured maximum).\n */\nexport interface AuditApi {\n /** Query audit log entries with optional flat filters and cursor-based pagination. */\n queryLogs(filters?: ConsoleQueryFilters): Promise<ConsoleQueryResult>;\n /** Retrieve a single audit log entry by its ID. Returns `null` when not found. */\n getLog(id: string): Promise<AuditLog | null>;\n /** Get aggregated audit statistics. Requires adapter.getStats. */\n getStats(options?: { since?: Date }): Promise<AuditStats>;\n /** Get serializable summaries of all registered enrichments. */\n getEnrichments(): EnrichmentSummary[];\n /** Export logs as CSV or JSON string. */\n exportLogs(filters?: ConsoleQueryFilters, format?: \"csv\" | \"json\"): Promise<string>;\n /** Purge audit logs before a given date. Requires adapter.purgeLogs. */\n purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }>;\n}\n\nconst CSV_HEADERS = [\n \"id\",\n \"timestamp\",\n \"tableName\",\n \"operation\",\n \"recordId\",\n \"actorId\",\n \"severity\",\n \"label\",\n \"description\",\n] as const;\n\nfunction escapeCsvField(value: string): string {\n if (value.includes('\"') || value.includes(\",\") || value.includes(\"\\n\") || value.includes(\"\\r\")) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\nfunction logToCsvRow(log: AuditLog): string {\n const fields = [\n log.id,\n log.timestamp instanceof Date ? log.timestamp.toISOString() : String(log.timestamp),\n log.tableName,\n log.operation,\n log.recordId,\n log.actorId ?? \"\",\n log.severity ?? \"\",\n log.label ?? \"\",\n log.description ?? \"\",\n ];\n return fields.map(escapeCsvField).join(\",\");\n}\n\nfunction toTimeFilter(date: Date): TimeFilter {\n return { date };\n}\n\nfunction buildQuerySpec(filters: ConsoleQueryFilters, effectiveLimit: number): AuditQuerySpec {\n const limit = Math.min(filters.limit ?? effectiveLimit, effectiveLimit);\n\n const spec: AuditQuerySpec = {\n filters: {},\n limit,\n };\n\n if (filters.tableName !== undefined) {\n spec.filters.resource = { tableName: filters.tableName };\n }\n if (filters.actorId !== undefined) {\n spec.filters.actorIds = [filters.actorId];\n }\n if (filters.severity !== undefined) {\n spec.filters.severities = [filters.severity as AuditSeverity];\n }\n if (filters.compliance !== undefined) {\n spec.filters.compliance = [filters.compliance];\n }\n if (filters.operation !== undefined) {\n spec.filters.operations = [filters.operation.toUpperCase() as \"INSERT\" | \"UPDATE\" | \"DELETE\"];\n }\n if (filters.since !== undefined) {\n spec.filters.since = toTimeFilter(filters.since);\n }\n if (filters.until !== undefined) {\n spec.filters.until = toTimeFilter(filters.until);\n }\n if (filters.search !== undefined) {\n spec.filters.searchText = filters.search;\n }\n if (filters.cursor !== undefined) {\n spec.cursor = filters.cursor;\n }\n\n return spec;\n}\n\nexport function createAuditApi(\n adapter: AuditDatabaseAdapter,\n registry: EnrichmentRegistry,\n maxQueryLimit?: number,\n): AuditApi {\n const effectiveLimit = maxQueryLimit ?? 1000;\n\n function requireQueryLogs(): NonNullable<AuditDatabaseAdapter[\"queryLogs\"]> {\n if (adapter.queryLogs === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n return adapter.queryLogs;\n }\n\n function requireGetLogById(): NonNullable<AuditDatabaseAdapter[\"getLogById\"]> {\n if (adapter.getLogById === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements getLogById(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n return adapter.getLogById;\n }\n\n function requireGetStats(): NonNullable<AuditDatabaseAdapter[\"getStats\"]> {\n if (adapter.getStats === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements getStats(). \" +\n \"Check that your ORM adapter supports statistics.\",\n );\n }\n return adapter.getStats;\n }\n\n function requirePurgeLogs(): NonNullable<AuditDatabaseAdapter[\"purgeLogs\"]> {\n if (adapter.purgeLogs === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements purgeLogs(). \" +\n \"Check that your ORM adapter supports log purging.\",\n );\n }\n return adapter.purgeLogs;\n }\n\n async function queryLogs(filters?: ConsoleQueryFilters): Promise<ConsoleQueryResult> {\n const queryFn = requireQueryLogs();\n const spec = buildQuerySpec(filters ?? {}, effectiveLimit);\n const result = await queryFn(spec);\n return {\n entries: result.entries,\n ...(result.nextCursor !== undefined && { nextCursor: result.nextCursor }),\n hasNextPage: result.nextCursor !== undefined,\n };\n }\n\n async function getLog(id: string): Promise<AuditLog | null> {\n const getLogFn = requireGetLogById();\n return getLogFn(id);\n }\n\n async function getStats(options?: { since?: Date }): Promise<AuditStats> {\n const getStatsFn = requireGetStats();\n return getStatsFn(options);\n }\n\n function getEnrichments(): EnrichmentSummary[] {\n const entries = registry.getEntries();\n const summaries: EnrichmentSummary[] = [];\n\n for (const entry of entries) {\n for (const config of entry.configs) {\n const summary: EnrichmentSummary = {\n table: entry.table,\n operation: entry.operation,\n };\n if (config.label !== undefined) {\n summary.label = config.label;\n }\n if (config.severity !== undefined) {\n summary.severity = config.severity;\n }\n if (config.compliance !== undefined) {\n summary.compliance = config.compliance;\n }\n if (config.notify !== undefined) {\n summary.notify = config.notify;\n }\n if (config.redact !== undefined) {\n summary.redact = config.redact;\n }\n if (config.include !== undefined) {\n summary.include = config.include;\n }\n summaries.push(summary);\n }\n }\n\n return summaries;\n }\n\n async function exportLogs(filters?: ConsoleQueryFilters, format?: \"csv\" | \"json\"): Promise<string> {\n const exportFormat = format ?? \"json\";\n const exportFilters: ConsoleQueryFilters = { ...filters, limit: effectiveLimit };\n const result = await queryLogs(exportFilters);\n\n if (exportFormat === \"json\") {\n return JSON.stringify(result.entries);\n }\n\n const rows = [CSV_HEADERS.join(\",\")];\n for (const log of result.entries) {\n rows.push(logToCsvRow(log));\n }\n return rows.join(\"\\n\");\n }\n\n async function purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }> {\n const purgeFn = requirePurgeLogs();\n return purgeFn(options);\n }\n\n return { queryLogs, getLog, getStats, getEnrichments, exportLogs, purgeLogs };\n}\n","import type { ConsoleProductEndpoint, ConsoleProductRequest } from \"@usebetterdev/console-contract\";\nimport type { AuditApi, ConsoleQueryFilters } from \"./audit-api.js\";\n\nconst MAX_PARAM_LENGTH = 1000;\n\nfunction exceedsMaxLength(value: string | undefined): boolean {\n return value !== undefined && value.length > MAX_PARAM_LENGTH;\n}\n\nfunction parsePositiveInt(value: string | undefined, max: number): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return undefined;\n }\n return Math.min(Math.floor(parsed), max);\n}\n\nfunction parseIsoDate(value: string | undefined): Date | undefined {\n if (value === undefined) {\n return undefined;\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return undefined;\n }\n return date;\n}\n\nfunction parseConsoleQueryFilters(query: Record<string, string>): ConsoleQueryFilters {\n const filters: ConsoleQueryFilters = {};\n\n const limit = parsePositiveInt(query.limit, 1000);\n if (limit !== undefined) {\n filters.limit = limit;\n }\n if (query.tableName !== undefined) {\n filters.tableName = query.tableName;\n }\n if (query.operation !== undefined) {\n filters.operation = query.operation;\n }\n if (query.actorId !== undefined) {\n filters.actorId = query.actorId;\n }\n if (query.severity !== undefined) {\n filters.severity = query.severity;\n }\n if (query.compliance !== undefined) {\n filters.compliance = query.compliance;\n }\n if (query.search !== undefined) {\n filters.search = query.search;\n }\n if (query.cursor !== undefined) {\n filters.cursor = query.cursor;\n }\n\n const since = parseIsoDate(query.since);\n if (since !== undefined) {\n filters.since = since;\n }\n const until = parseIsoDate(query.until);\n if (until !== undefined) {\n filters.until = until;\n }\n\n return filters;\n}\n\nfunction hasLongQueryParam(query: Record<string, string>): boolean {\n return (\n exceedsMaxLength(query.tableName) ||\n exceedsMaxLength(query.actorId) ||\n exceedsMaxLength(query.cursor) ||\n exceedsMaxLength(query.operation) ||\n exceedsMaxLength(query.severity) ||\n exceedsMaxLength(query.compliance) ||\n exceedsMaxLength(query.search)\n );\n}\n\nexport function createAuditConsoleEndpoints(\n api: AuditApi,\n): ConsoleProductEndpoint[] {\n return [\n {\n method: \"GET\",\n path: \"/logs\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n if (hasLongQueryParam(request.query)) {\n return { status: 400, body: { error: \"Query parameter exceeds maximum length\" } };\n }\n const filters = parseConsoleQueryFilters(request.query);\n const result = await api.queryLogs(filters);\n return { status: 200, body: result };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/logs/:id\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const id = request.params.id?.trim();\n if (!id) {\n return { status: 400, body: { error: \"Missing log id\" } };\n }\n const log = await api.getLog(id);\n if (!log) {\n return { status: 404, body: { error: \"Audit log not found\" } };\n }\n return { status: 200, body: log };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/stats\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const options: { since?: Date } = {};\n const since = parseIsoDate(request.query.since);\n if (since !== undefined) {\n options.since = since;\n }\n const stats = await api.getStats(options);\n return { status: 200, body: stats };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/enrichments\",\n requiredPermission: \"read\",\n async handler() {\n try {\n const enrichments = api.getEnrichments();\n return { status: 200, body: enrichments };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/export\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const format = request.query.format;\n if (format !== undefined && format !== \"csv\" && format !== \"json\") {\n return { status: 400, body: { error: \"Invalid format. Must be 'csv' or 'json'\" } };\n }\n if (hasLongQueryParam(request.query)) {\n return { status: 400, body: { error: \"Query parameter exceeds maximum length\" } };\n }\n const filters = parseConsoleQueryFilters(request.query);\n const data = await api.exportLogs(filters, format as \"csv\" | \"json\" | undefined);\n return { status: 200, body: data };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n {\n method: \"DELETE\",\n path: \"/logs\",\n requiredPermission: \"admin\",\n async handler(request: ConsoleProductRequest) {\n try {\n const body = request.body as Record<string, unknown> | undefined;\n const beforeValue = body?.before;\n if (beforeValue === undefined || beforeValue === null) {\n return { status: 400, body: { error: \"Missing required field: before\" } };\n }\n const before = new Date(beforeValue as string);\n if (Number.isNaN(before.getTime())) {\n return { status: 400, body: { error: \"Invalid date for 'before' field\" } };\n }\n const options: { before: Date; tableName?: string } = { before };\n if (typeof body?.tableName === \"string\" && body.tableName.length > 0) {\n options.tableName = body.tableName;\n }\n const result = await api.purgeLogs(options);\n return { status: 200, body: result };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } };\n }\n },\n },\n ];\n}\n","import type {\n AfterLogHook,\n AuditContext,\n AuditLog,\n AuditOperation,\n BeforeLogHook,\n BetterAuditConfig,\n BetterAuditInstance,\n CaptureLogInput,\n EnrichmentConfig,\n} from \"./types.js\";\nimport { getAuditContext, runWithAuditContext } from \"./context.js\";\nimport { computeDiff } from \"./diff.js\";\nimport { EnrichmentRegistry, applyEnrichment } from \"./enrichment-registry.js\";\nimport { normalizeInput } from \"./normalize.js\";\nimport { AuditQueryBuilder } from \"./query-builder.js\";\nimport { createAuditApi } from \"./audit-api.js\";\nimport { createAuditConsoleEndpoints } from \"./console-endpoints.js\";\n\nfunction withContext<T>(\n context: AuditContext,\n fn: () => Promise<T>,\n): Promise<T> {\n return runWithAuditContext(context, fn);\n}\n\nexport function betterAudit(config: BetterAuditConfig): BetterAuditInstance {\n const { database } = config;\n const auditTables = new Set(config.auditTables);\n const registry = new EnrichmentRegistry();\n const beforeLogHooks: BeforeLogHook[] = config.beforeLog !== undefined ? [...config.beforeLog] : [];\n const afterLogHooks: AfterLogHook[] = config.afterLog !== undefined ? [...config.afterLog] : [];\n\n function enrich(\n table: string,\n operation: AuditOperation | \"*\",\n enrichmentConfig: EnrichmentConfig,\n ): void {\n if (table !== \"*\" && !auditTables.has(table)) {\n throw new Error(\n `Cannot register enrichment for table \"${table}\": it is not in auditTables. ` +\n `Registered tables: ${[...auditTables].join(\", \")}`,\n );\n }\n\n registry.register(table, operation, enrichmentConfig);\n }\n\n function onBeforeLog(hook: BeforeLogHook): () => void {\n beforeLogHooks.push(hook);\n return () => {\n const index = beforeLogHooks.indexOf(hook);\n if (index !== -1) {\n beforeLogHooks.splice(index, 1);\n }\n };\n }\n\n function onAfterLog(hook: AfterLogHook): () => void {\n afterLogHooks.push(hook);\n return () => {\n const index = afterLogHooks.indexOf(hook);\n if (index !== -1) {\n afterLogHooks.splice(index, 1);\n }\n };\n }\n\n async function writeAndRunAfterHooks(log: AuditLog): Promise<void> {\n await database.writeLog(log);\n for (const hook of afterLogHooks) {\n await hook(log);\n }\n }\n\n async function captureLog(input: CaptureLogInput): Promise<void> {\n // 1. Early return if table not in auditTables\n if (!auditTables.has(input.tableName)) {\n return;\n }\n\n if (input.recordId === \"\") {\n throw new Error(\"captureLog requires a non-empty recordId\");\n }\n\n // 2. Normalize input based on operation type\n const normalized = normalizeInput(input.operation, input.before, input.after);\n\n const context = getAuditContext();\n\n // 3. Assemble log (merge input + AuditContext)\n // Conditional spreads satisfy exactOptionalPropertyTypes — properties\n // are either absent or carry a narrowed non-undefined value.\n const actorId = input.actorId ?? context?.actorId;\n const label = input.label ?? context?.label;\n const reason = input.reason ?? context?.reason;\n const compliance = input.compliance ?? context?.compliance;\n const metadata = input.metadata ?? context?.metadata;\n\n const log: AuditLog = {\n id: crypto.randomUUID(),\n timestamp: new Date(),\n tableName: input.tableName,\n operation: input.operation,\n recordId: input.recordId,\n ...(actorId !== undefined && { actorId }),\n ...(label !== undefined && { label }),\n ...(reason !== undefined && { reason }),\n ...(compliance !== undefined && { compliance }),\n ...(metadata !== undefined && { metadata }),\n ...(input.description !== undefined && { description: input.description }),\n ...(input.severity !== undefined && { severity: input.severity }),\n ...(input.notify !== undefined && { notify: input.notify }),\n ...(normalized.before !== undefined && { beforeData: { ...normalized.before } }),\n ...(normalized.after !== undefined && { afterData: { ...normalized.after } }),\n };\n\n // 4. Compute diff only for UPDATE operations\n if (input.operation === \"UPDATE\") {\n const diff = computeDiff(normalized.before, normalized.after);\n if (diff.changedFields.length > 0) {\n log.diff = diff;\n }\n }\n\n // 5. Resolve enrichment → Redact → Describe → Apply scalars\n const resolved = registry.resolve(input.tableName, input.operation);\n if (resolved !== undefined) {\n applyEnrichment(log, resolved);\n }\n\n // 6. Run beforeLog hooks (sequential, may mutate log, errors abort)\n for (const hook of beforeLogHooks) {\n await hook(log);\n }\n\n // 7. Write log (sync or async) + afterLog hooks\n const isAsync = input.asyncWrite ?? config.asyncWrite ?? false;\n if (isAsync) {\n void writeAndRunAfterHooks(log).catch((error: unknown) => {\n if (config.onError !== undefined) {\n config.onError(error);\n } else {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`audit: async write failed for ${log.tableName}/${log.id} — ${message}`);\n }\n });\n } else {\n await writeAndRunAfterHooks(log);\n }\n }\n\n function query(): AuditQueryBuilder {\n if (database.queryLogs === undefined) {\n throw new Error(\n \"audit.query() requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n const queryLogs = database.queryLogs;\n return new AuditQueryBuilder(\n (spec) => queryLogs(spec),\n undefined,\n undefined,\n undefined,\n config.maxQueryLimit,\n );\n }\n\n if (config.console) {\n const api = createAuditApi(database, registry, config.maxQueryLimit);\n const endpoints = createAuditConsoleEndpoints(api);\n config.console.registerProduct({\n id: \"audit\",\n name: \"Better Audit\",\n endpoints,\n });\n }\n\n return { captureLog, query, withContext, enrich, onBeforeLog, onAfterLog };\n}\n","/**\n * Logical schema for the `audit_logs` table.\n *\n * ORM adapters translate this into their own migration format.\n * Core never runs SQL — this is a declarative data structure only.\n */\n\nexport type ColumnType =\n | \"uuid\"\n | \"timestamptz\"\n | \"text\"\n | \"jsonb\"\n | \"boolean\";\n\nexport interface ColumnDefinition {\n type: ColumnType;\n nullable: boolean;\n primaryKey?: boolean;\n defaultExpression?: string;\n indexed?: boolean;\n description: string;\n}\n\n/**\n * Column names in the `audit_logs` table use snake_case.\n * These map to camelCase properties in the `AuditLog` TypeScript type:\n *\n * | Column (snake_case) | AuditLog property (camelCase) |\n * |---------------------|-------------------------------|\n * | table_name | tableName |\n * | record_id | recordId |\n * | actor_id | actorId |\n * | before_data | beforeData |\n * | after_data | afterData |\n * | redacted_fields | redactedFields |\n */\nexport type AuditLogColumnName =\n | \"id\"\n | \"timestamp\"\n | \"table_name\"\n | \"operation\"\n | \"record_id\"\n | \"actor_id\"\n | \"before_data\"\n | \"after_data\"\n | \"diff\"\n | \"label\"\n | \"description\"\n | \"severity\"\n | \"compliance\"\n | \"notify\"\n | \"reason\"\n | \"metadata\"\n | \"redacted_fields\";\n\nexport interface AuditLogSchema {\n tableName: string;\n columns: Record<string, ColumnDefinition>;\n}\n\nexport const AUDIT_LOG_SCHEMA: AuditLogSchema = {\n tableName: \"audit_logs\",\n columns: {\n id: {\n type: \"uuid\",\n nullable: false,\n primaryKey: true,\n defaultExpression: \"gen_random_uuid()\",\n description: \"Unique identifier for the audit log entry\",\n },\n timestamp: {\n type: \"timestamptz\",\n nullable: false,\n defaultExpression: \"now()\",\n indexed: true,\n description: \"When the audited event occurred\",\n },\n table_name: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Name of the table that was modified\",\n },\n operation: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Type of operation: INSERT, UPDATE, or DELETE\",\n },\n record_id: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Primary key of the affected record\",\n },\n actor_id: {\n type: \"text\",\n nullable: true,\n indexed: true,\n description: \"Identifier of the user or system that performed the action\",\n },\n before_data: {\n type: \"jsonb\",\n nullable: true,\n description: \"Row snapshot before the mutation (DELETE and UPDATE only)\",\n },\n after_data: {\n type: \"jsonb\",\n nullable: true,\n description: \"Row snapshot after the mutation (INSERT and UPDATE only)\",\n },\n diff: {\n type: \"jsonb\",\n nullable: true,\n description: \"Changed field names for UPDATE operations\",\n },\n label: {\n type: \"text\",\n nullable: true,\n description: \"Human-readable label for the event\",\n },\n description: {\n type: \"text\",\n nullable: true,\n description: \"Detailed description of what happened\",\n },\n severity: {\n type: \"text\",\n nullable: true,\n description: \"Severity level: low, medium, high, or critical\",\n },\n compliance: {\n type: \"jsonb\",\n nullable: true,\n description: \"Compliance framework tags (e.g. soc2, gdpr, hipaa)\",\n },\n notify: {\n type: \"boolean\",\n nullable: true,\n description: \"Whether this event should trigger notifications\",\n },\n reason: {\n type: \"text\",\n nullable: true,\n description: \"Justification or reason for the action\",\n },\n metadata: {\n type: \"jsonb\",\n nullable: true,\n description: \"Arbitrary key-value metadata attached to the event\",\n },\n redacted_fields: {\n type: \"jsonb\",\n nullable: true,\n description: \"Field names that were removed by redaction rules\",\n },\n },\n};\n","import type { AuditContext } from \"./types.js\";\nimport { runWithAuditContext } from \"./context.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Function that extracts a string value from a Web Request.\n * Return undefined if the value cannot be extracted.\n * May be sync or async.\n */\nexport type ValueExtractor = (\n request: Request,\n) => string | undefined | Promise<string | undefined>;\n\n/**\n * Describes how to extract audit identity from an incoming HTTP request.\n * All fields are optional — if omitted, the corresponding context field\n * will be undefined.\n */\nexport interface ContextExtractor {\n /** Extracts the actor identifier (user / service account). */\n actor?: ValueExtractor;\n}\n\n/**\n * Options for `handleMiddleware`.\n */\nexport interface MiddlewareHandlerOptions {\n /** Called when an extractor throws. Defaults to silent fail-open. */\n onError?: (error: unknown) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Built-in extractors\n// ---------------------------------------------------------------------------\n\n/**\n * Decode a JWT payload without verification.\n * Uses only Web-standard APIs (atob) — safe on edge runtimes.\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split(\".\");\n if (parts.length < 2) {\n return null;\n }\n const payload = parts[1];\n if (!payload) {\n return null;\n }\n let base64 = payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (base64.length % 4 !== 0) {\n base64 += \"=\";\n }\n const decoded = atob(base64);\n const parsed: unknown = JSON.parse(decoded);\n if (typeof parsed !== \"object\" || parsed === null) {\n return null;\n }\n return parsed as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Extracts a JWT claim from the `Authorization: Bearer <token>` header.\n *\n * Decodes the token **without** signature verification — signing is the\n * auth layer's responsibility. This is intentional: the audit layer only\n * needs the identity, not proof of authenticity.\n *\n * @param claim - JWT claim name to extract (e.g. `\"sub\"`, `\"tenant_id\"`)\n */\nexport function fromBearerToken(claim: string): ValueExtractor {\n return (request: Request) => {\n const authorization = request.headers.get(\"authorization\");\n if (!authorization) {\n return undefined;\n }\n const parts = authorization.split(\" \");\n if (parts.length !== 2 || parts[0]?.toLowerCase() !== \"bearer\") {\n return undefined;\n }\n const token = parts[1];\n if (!token) {\n return undefined;\n }\n const payload = decodeJwtPayload(token);\n if (!payload) {\n return undefined;\n }\n const value = payload[claim];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n };\n}\n\n/**\n * Extracts a value from a named cookie in the `Cookie` header.\n *\n * The raw cookie value is returned as-is. To resolve it into a user identity,\n * compose with a resolver function (e.g. look up a session in a database).\n *\n * @param cookieName - Name of the cookie to read\n */\nexport function fromCookie(cookieName: string): ValueExtractor {\n return (request: Request) => {\n const cookieHeader = request.headers.get(\"cookie\");\n if (!cookieHeader) {\n return undefined;\n }\n const cookies = cookieHeader.split(\";\");\n for (const cookie of cookies) {\n const separatorIndex = cookie.indexOf(\"=\");\n if (separatorIndex === -1) {\n continue;\n }\n const name = cookie.slice(0, separatorIndex).trim();\n if (name === cookieName) {\n const value = cookie.slice(separatorIndex + 1).trim();\n return value.length > 0 ? value : undefined;\n }\n }\n return undefined;\n };\n}\n\n/**\n * Extracts a value from a custom request header.\n *\n * @param headerName - Header name (case-insensitive per the Web API)\n */\nexport function fromHeader(headerName: string): ValueExtractor {\n return (request: Request) => {\n const value = request.headers.get(headerName);\n if (!value || value.trim().length === 0) {\n return undefined;\n }\n return value.trim();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared middleware handler\n// ---------------------------------------------------------------------------\n\n/**\n * Runs an extractor safely, catching errors and returning undefined on failure.\n */\nasync function safeExtract(\n extractor: ValueExtractor | undefined,\n request: Request,\n onError: ((error: unknown) => void) | undefined,\n): Promise<string | undefined> {\n if (!extractor) {\n return undefined;\n }\n try {\n return await extractor(request);\n } catch (error: unknown) {\n if (onError) {\n onError(error);\n }\n return undefined;\n }\n}\n\n/**\n * Shared middleware handler used by all framework adapters.\n *\n * 1. Extracts actor from the request using the provided extractor\n * 2. Builds an `AuditContext` (undefined if nothing was extracted)\n * 3. Wraps `next()` inside `runWithAuditContext()` if context is available\n * 4. Calls `next()` without context if extraction yields nothing\n *\n * Extraction failures never break the request (fail open).\n */\nexport async function handleMiddleware(\n extractor: ContextExtractor,\n request: Request,\n next: () => Promise<void>,\n options: MiddlewareHandlerOptions = {},\n): Promise<void> {\n const actorId = await safeExtract(extractor.actor, request, options.onError);\n\n if (actorId === undefined) {\n await next();\n return;\n }\n\n const auditContext: AuditContext = { actorId };\n\n await runWithAuditContext(auditContext, () => next());\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/context.ts","../src/diff.ts","../src/enrichment-registry.ts","../src/normalize.ts","../src/duration.ts","../src/query-builder.ts","../src/audit-api.ts","../../../shared/console-utils/src/index.ts","../src/console-endpoints.ts","../src/better-audit.ts","../src/audit-log-schema.ts","../src/context-extractor.ts"],"sourcesContent":["import type { AuditContext } from \"./types.js\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst storage = new AsyncLocalStorage<AuditContext>();\n\n/**\n * Returns the current audit context, or undefined when not inside a request\n * scope (middleware or withContext).\n */\nexport function getAuditContext(): AuditContext | undefined {\n return storage.getStore();\n}\n\n/**\n * Run fn inside a scope where getAuditContext() returns the given context.\n */\nexport function runWithAuditContext<T>(\n context: AuditContext,\n fn: () => T | Promise<T>,\n): Promise<T> {\n return Promise.resolve(storage.run(context, () => fn()));\n}\n\n/**\n * Merge additional context into the current scope and run fn.\n * If no scope exists, a new one is created from the partial context.\n * Properties in override take precedence over the existing context.\n */\nexport function mergeAuditContext<T>(\n override: Partial<AuditContext>,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const current = storage.getStore() ?? {};\n const merged: AuditContext = { ...current, ...override };\n return Promise.resolve(storage.run(merged, () => fn()));\n}\n","/**\n * Compute which fields changed between two row snapshots.\n * Uses JSON.stringify for deep equality — order-sensitive for objects/arrays.\n */\nexport function computeDiff(\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined,\n): { changedFields: string[] } {\n const b = before ?? {};\n const a = after ?? {};\n const allKeys = new Set([...Object.keys(b), ...Object.keys(a)]);\n const changedFields: string[] = [];\n\n for (const key of allKeys) {\n const inBefore = Object.prototype.hasOwnProperty.call(b, key);\n const inAfter = Object.prototype.hasOwnProperty.call(a, key);\n if (!inBefore || !inAfter) {\n changedFields.push(key);\n continue;\n }\n try {\n if (JSON.stringify(b[key]) !== JSON.stringify(a[key])) {\n changedFields.push(key);\n }\n } catch {\n // Non-serializable value (e.g. circular reference) — treat as changed\n changedFields.push(key);\n }\n }\n\n return { changedFields };\n}\n","import type {\n AuditLog,\n AuditOperation,\n AuditSeverity,\n EnrichmentConfig,\n EnrichmentDescriptionContext,\n} from \"./types.js\";\n\n/**\n * Specificity tiers for enrichment resolution (least → most specific):\n *\n * 1. \"*:*\" — global catch-all\n * 2. \"*:OP\" — any table, specific operation\n * 3. \"table:*\" — specific table, any operation\n * 4. \"table:OP\" — exact match\n *\n * A table-scoped rule is more specific than an operation-scoped rule because\n * tables are the primary organizational unit for audit policies. A rule like\n * \"users:*\" (all ops on users) should override \"*:DELETE\" (all deletes) for\n * scalar fields, since the policy author has explicitly targeted that table.\n */\n\nfunction makeKey(table: string, operation: string): string {\n return `${table}:${operation.toUpperCase()}`;\n}\n\nfunction validateRedactInclude(config: EnrichmentConfig, key: string): void {\n if (\n config.redact !== undefined &&\n config.redact.length > 0 &&\n config.include !== undefined &&\n config.include.length > 0\n ) {\n throw new Error(\n `Enrichment for \"${key}\" cannot specify both \"redact\" and \"include\". ` +\n \"Use one or the other.\",\n );\n }\n}\n\n/** Resolved enrichment config after merging all matching tiers. */\nexport interface ResolvedEnrichment {\n label?: string;\n description?: (context: EnrichmentDescriptionContext) => string;\n severity?: AuditSeverity;\n compliance?: string[];\n notify?: boolean;\n redact?: string[];\n include?: string[];\n}\n\nexport class EnrichmentRegistry {\n private readonly entries = new Map<string, EnrichmentConfig[]>();\n\n register(table: string, operation: AuditOperation | \"*\", config: EnrichmentConfig): void {\n const key = makeKey(table, operation);\n validateRedactInclude(config, key);\n\n const existing = this.entries.get(key);\n if (existing !== undefined) {\n existing.push(config);\n } else {\n this.entries.set(key, [config]);\n }\n }\n\n getEntries(): Array<{ table: string; operation: string; configs: EnrichmentConfig[] }> {\n const result: Array<{ table: string; operation: string; configs: EnrichmentConfig[] }> = [];\n for (const [key, configs] of this.entries) {\n const separatorIndex = key.indexOf(\":\");\n const table = key.slice(0, separatorIndex);\n const operation = key.slice(separatorIndex + 1);\n result.push({ table, operation, configs });\n }\n return result;\n }\n\n resolve(\n table: string,\n operation: string,\n ): ResolvedEnrichment | undefined {\n const normalizedOp = operation.toUpperCase();\n\n // Collect configs from all matching tiers in specificity order\n const keysToCheck = [\n makeKey(\"*\", \"*\"),\n makeKey(\"*\", normalizedOp),\n makeKey(table, \"*\"),\n makeKey(table, normalizedOp),\n ];\n\n const allConfigs: EnrichmentConfig[] = [];\n for (const key of keysToCheck) {\n const configs = this.entries.get(key);\n if (configs !== undefined) {\n for (const config of configs) {\n allConfigs.push(config);\n }\n }\n }\n\n if (allConfigs.length === 0) {\n return undefined;\n }\n\n return mergeEnrichmentConfigs(allConfigs, `${table}:${normalizedOp}`);\n }\n}\n\n/**\n * Merge multiple enrichment configs in order (earlier = less specific).\n * - Scalars: last-write-wins (more specific overrides)\n * - Arrays: concatenate & deduplicate\n * - `description` function: last-write-wins\n *\n * Throws if the merged result contains both `redact` and `include`.\n */\nexport function mergeEnrichmentConfigs(\n configs: readonly EnrichmentConfig[],\n contextKey: string,\n): ResolvedEnrichment {\n const result: ResolvedEnrichment = {};\n\n for (const config of configs) {\n if (config.label !== undefined) {\n result.label = config.label;\n }\n if (config.description !== undefined) {\n result.description = config.description;\n }\n if (config.severity !== undefined) {\n result.severity = config.severity;\n }\n if (config.notify !== undefined) {\n result.notify = config.notify;\n }\n\n if (config.compliance !== undefined) {\n if (result.compliance !== undefined) {\n const merged = [...result.compliance, ...config.compliance];\n result.compliance = [...new Set(merged)];\n } else {\n result.compliance = [...config.compliance];\n }\n }\n\n if (config.redact !== undefined) {\n if (result.redact !== undefined) {\n const merged = [...result.redact, ...config.redact];\n result.redact = [...new Set(merged)];\n } else {\n result.redact = [...config.redact];\n }\n }\n\n if (config.include !== undefined) {\n if (result.include !== undefined) {\n const merged = [...result.include, ...config.include];\n result.include = [...new Set(merged)];\n } else {\n result.include = [...config.include];\n }\n }\n }\n\n // Post-merge conflict check: redact + include from different registrations\n if (\n result.redact !== undefined &&\n result.redact.length > 0 &&\n result.include !== undefined &&\n result.include.length > 0\n ) {\n throw new Error(\n `Enrichment merge for \"${contextKey}\" produced both \"redact\" and \"include\". ` +\n \"These are mutually exclusive — fix the conflicting registrations.\",\n );\n }\n\n return result;\n}\n\n/**\n * Remove or filter fields from beforeData, afterData, and diff.changedFields.\n * Operates on the log in place.\n */\nexport function applyFieldRedaction(\n log: AuditLog,\n resolved: ResolvedEnrichment,\n): void {\n const { redact, include } = resolved;\n const removedFields = new Set<string>();\n\n if (redact !== undefined && redact.length > 0) {\n const redactSet = new Set(redact);\n\n if (log.beforeData !== undefined) {\n for (const key of Object.keys(log.beforeData)) {\n if (redactSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.beforeData = filterOutKeys(log.beforeData, redactSet);\n }\n if (log.afterData !== undefined) {\n for (const key of Object.keys(log.afterData)) {\n if (redactSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.afterData = filterOutKeys(log.afterData, redactSet);\n }\n if (log.diff !== undefined) {\n log.diff = {\n changedFields: log.diff.changedFields.filter(\n (field) => !redactSet.has(field),\n ),\n };\n }\n } else if (include !== undefined && include.length > 0) {\n const includeSet = new Set(include);\n\n if (log.beforeData !== undefined) {\n for (const key of Object.keys(log.beforeData)) {\n if (!includeSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.beforeData = keepOnlyKeys(log.beforeData, includeSet);\n }\n if (log.afterData !== undefined) {\n for (const key of Object.keys(log.afterData)) {\n if (!includeSet.has(key)) {\n removedFields.add(key);\n }\n }\n log.afterData = keepOnlyKeys(log.afterData, includeSet);\n }\n if (log.diff !== undefined) {\n log.diff = {\n changedFields: log.diff.changedFields.filter((field) =>\n includeSet.has(field),\n ),\n };\n }\n }\n\n if (removedFields.size > 0) {\n log.redactedFields = [...removedFields].sort();\n }\n}\n\n/**\n * Apply resolved enrichment to an AuditLog.\n * Enrichment values only fill gaps — explicit per-call and context values take precedence.\n *\n * Flow:\n * 1. Redact fields (before description sees data)\n * 2. Call description function with post-redaction context\n * 3. Apply scalar/array enrichment fields\n */\nexport function applyEnrichment(\n log: AuditLog,\n resolved: ResolvedEnrichment,\n): void {\n // Step 1: Redact fields first\n applyFieldRedaction(log, resolved);\n\n // Step 2: Call description with post-redaction data\n if (resolved.description !== undefined && log.description === undefined) {\n try {\n const descriptionContext: EnrichmentDescriptionContext = {\n before: log.beforeData !== undefined ? structuredClone(log.beforeData) : undefined,\n after: log.afterData !== undefined ? structuredClone(log.afterData) : undefined,\n diff: log.diff !== undefined ? structuredClone(log.diff) : undefined,\n actorId: log.actorId,\n metadata: log.metadata !== undefined ? structuredClone(log.metadata) : undefined,\n };\n\n log.description = resolved.description(descriptionContext);\n } catch {\n // Description function or data cloning threw — leave description unset, log still gets written\n }\n }\n\n // Step 3: Apply scalar/array enrichment (only if not already set)\n if (resolved.label !== undefined && log.label === undefined) {\n log.label = resolved.label;\n }\n if (resolved.severity !== undefined && log.severity === undefined) {\n log.severity = resolved.severity;\n }\n if (resolved.notify !== undefined && log.notify === undefined) {\n log.notify = resolved.notify;\n }\n if (resolved.compliance !== undefined) {\n if (log.compliance !== undefined) {\n const merged = [...log.compliance, ...resolved.compliance];\n log.compliance = [...new Set(merged)];\n } else {\n log.compliance = [...resolved.compliance];\n }\n }\n}\n\nfunction filterOutKeys(\n data: Record<string, unknown>,\n keysToRemove: Set<string>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (!keysToRemove.has(key)) {\n result[key] = value;\n }\n }\n return result;\n}\n\nfunction keepOnlyKeys(\n data: Record<string, unknown>,\n keysToKeep: Set<string>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (keysToKeep.has(key)) {\n result[key] = value;\n }\n }\n return result;\n}\n","import type { AuditOperation } from \"./types.js\";\n\n/**\n * Normalize before/after data based on the operation type.\n *\n * - **INSERT** → `before` is dropped (only `after` is meaningful)\n * - **DELETE** → `after` is dropped (only `before` is meaningful)\n * - **UPDATE** → both are kept as-is\n */\nexport function normalizeInput(\n operation: AuditOperation,\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined,\n): {\n before: Record<string, unknown> | undefined;\n after: Record<string, unknown> | undefined;\n} {\n switch (operation) {\n case \"INSERT\": {\n return { before: undefined, after };\n }\n case \"DELETE\": {\n return { before, after: undefined };\n }\n case \"UPDATE\": {\n return { before, after };\n }\n }\n}\n","const DURATION_PATTERN = /^(\\d+)([hdwmy])$/;\n\ntype DurationUnit = \"h\" | \"d\" | \"w\" | \"m\" | \"y\";\n\nfunction isValidUnit(raw: string): raw is DurationUnit {\n return raw === \"h\" || raw === \"d\" || raw === \"w\" || raw === \"m\" || raw === \"y\";\n}\n\n/**\n * Parses a duration string (e.g. \"30d\", \"2w\", \"3m\", \"1y\") and returns\n * a Date that is `duration` before `referenceDate`.\n *\n * Supported units: h (hours), d (days), w (weeks), m (months), y (years).\n * Zero values are rejected.\n */\nexport function parseDuration(input: string, referenceDate?: Date): Date {\n const match = DURATION_PATTERN.exec(input);\n if (match === null) {\n throw new Error(\n `Invalid duration \"${input}\". Expected format: <number><unit> where unit is h, d, w, m, or y (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").`,\n );\n }\n\n const value = Number(match[1]);\n const rawUnit = match[2] as string;\n\n if (value === 0) {\n throw new Error(\n `Invalid duration \"${input}\". Value must be greater than zero.`,\n );\n }\n\n if (!isValidUnit(rawUnit)) {\n throw new Error(\n `Invalid duration unit \"${rawUnit}\". Expected h, d, w, m, or y.`,\n );\n }\n\n const result = referenceDate !== undefined ? new Date(referenceDate) : new Date();\n\n if (rawUnit === \"h\") {\n result.setHours(result.getHours() - value);\n } else if (rawUnit === \"d\") {\n result.setDate(result.getDate() - value);\n } else if (rawUnit === \"w\") {\n result.setDate(result.getDate() - value * 7);\n } else if (rawUnit === \"m\") {\n result.setMonth(result.getMonth() - value);\n } else {\n result.setFullYear(result.getFullYear() - value);\n }\n\n return result;\n}\n","import type { AuditOperation, AuditSeverity } from \"./types.js\";\nimport type {\n AuditQueryFilters,\n AuditQueryResult,\n AuditQuerySpec,\n TimeFilter,\n} from \"./query-types.js\";\nimport { parseDuration } from \"./duration.js\";\n\n/** Callback that executes a query spec against the adapter. */\nexport type QueryExecutor = (spec: AuditQuerySpec) => Promise<AuditQueryResult>;\n\nconst DEFAULT_MAX_QUERY_LIMIT = 1000;\nconst MAX_SEARCH_TEXT_LENGTH = 500;\n\n/**\n * Fluent, immutable query builder for audit logs.\n *\n * Each method returns a **new** instance — safe to fork and share.\n * Call `.list()` to execute, or `.toSpec()` to inspect without executing.\n */\nexport class AuditQueryBuilder {\n readonly #executor: QueryExecutor;\n readonly #filters: AuditQueryFilters;\n readonly #limit: number | undefined;\n readonly #cursor: string | undefined;\n readonly #maxLimit: number;\n readonly #sortOrder: \"asc\" | \"desc\" | undefined;\n\n constructor(\n executor: QueryExecutor,\n filters?: AuditQueryFilters,\n limit?: number,\n cursor?: string,\n maxLimit?: number,\n sortOrder?: \"asc\" | \"desc\",\n ) {\n this.#executor = executor;\n this.#filters = filters ?? {};\n this.#limit = limit;\n this.#cursor = cursor;\n this.#maxLimit = maxLimit ?? DEFAULT_MAX_QUERY_LIMIT;\n this.#sortOrder = sortOrder;\n }\n\n /** Filter by table name and optional record ID. Last-write-wins. */\n resource(tableName: string, recordId?: string): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n {\n ...this.#filters,\n resource: recordId !== undefined\n ? { tableName, recordId }\n : { tableName },\n },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Filter by actor IDs (OR semantics, deduplicates). */\n actor(...ids: string[]): AuditQueryBuilder {\n const existing = this.#filters.actorIds ?? [];\n const merged = [...new Set([...existing, ...ids])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, actorIds: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add severity levels to the filter (OR semantics, deduplicates). */\n severity(...levels: AuditSeverity[]): AuditQueryBuilder {\n const existing = this.#filters.severities ?? [];\n const merged = [...new Set([...existing, ...levels])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, severities: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add compliance tags to the filter (AND semantics, deduplicates). */\n compliance(...tags: string[]): AuditQueryBuilder {\n const existing = this.#filters.compliance ?? [];\n const merged = [...new Set([...existing, ...tags])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, compliance: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /**\n * Filter entries created after a point in time.\n *\n * Accepts a `Date` or a duration string (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").\n * Duration strings are eagerly validated but resolved at query time for a fresh \"now\".\n * Last-write-wins.\n */\n since(value: Date | string): AuditQueryBuilder {\n const filter = this.#parseTimeFilter(value);\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, since: filter },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /**\n * Filter entries created before a point in time.\n *\n * Accepts a `Date` or a duration string (e.g. \"4h\", \"30d\", \"2w\", \"3m\", \"1y\").\n * Duration strings are eagerly validated but resolved at query time for a fresh \"now\".\n * Last-write-wins.\n */\n until(value: Date | string): AuditQueryBuilder {\n const filter = this.#parseTimeFilter(value);\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, until: filter },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Full-text search filter. Last-write-wins. Max 500 characters. */\n search(text: string): AuditQueryBuilder {\n if (text.length > MAX_SEARCH_TEXT_LENGTH) {\n throw new Error(\n `searchText must be at most ${MAX_SEARCH_TEXT_LENGTH} characters, got ${text.length}`,\n );\n }\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, searchText: text },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Add operation types to the filter (OR semantics, deduplicates). */\n operation(...ops: AuditOperation[]): AuditQueryBuilder {\n const existing = this.#filters.operations ?? [];\n const merged = [...new Set([...existing, ...ops])];\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters, operations: merged },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set maximum number of entries to return. Must be > 0 and <= maxLimit. */\n limit(n: number): AuditQueryBuilder {\n if (n <= 0) {\n throw new Error(`limit must be greater than 0, got ${n}`);\n }\n if (n > this.#maxLimit) {\n throw new Error(\n `limit ${n} exceeds maximum allowed limit of ${this.#maxLimit}`,\n );\n }\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n n,\n this.#cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set the pagination cursor for fetching the next page. */\n after(cursor: string): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n this.#limit,\n cursor,\n this.#maxLimit,\n this.#sortOrder,\n );\n }\n\n /** Set the sort direction for results. */\n order(direction: \"asc\" | \"desc\"): AuditQueryBuilder {\n return new AuditQueryBuilder(\n this.#executor,\n { ...this.#filters },\n this.#limit,\n this.#cursor,\n this.#maxLimit,\n direction,\n );\n }\n\n /** Returns the query specification without executing. Useful for tests and debugging. */\n toSpec(): AuditQuerySpec {\n const filters: AuditQueryFilters = { ...this.#filters };\n // Deep-clone arrays so the returned spec is fully detached from builder internals\n if (filters.severities !== undefined) {\n filters.severities = [...filters.severities];\n }\n if (filters.operations !== undefined) {\n filters.operations = [...filters.operations];\n }\n if (filters.compliance !== undefined) {\n filters.compliance = [...filters.compliance];\n }\n if (filters.actorIds !== undefined) {\n filters.actorIds = [...filters.actorIds];\n }\n const spec: AuditQuerySpec = { filters };\n const effectiveLimit = this.#limit ?? this.#maxLimit;\n spec.limit = effectiveLimit;\n if (this.#cursor !== undefined) {\n spec.cursor = this.#cursor;\n }\n if (this.#sortOrder !== undefined) {\n spec.sortOrder = this.#sortOrder;\n }\n return spec;\n }\n\n /** Execute the query against the adapter. */\n list(): Promise<AuditQueryResult> {\n return this.#executor(this.toSpec());\n }\n\n #parseTimeFilter(value: Date | string): TimeFilter {\n if (value instanceof Date) {\n return { date: value };\n }\n // Eagerly validate — throws if format is invalid\n parseDuration(value);\n return { duration: value };\n }\n}\n","import type { AuditDatabaseAdapter, AuditLog, AuditStats, AuditSeverity } from \"./types.js\";\nimport type { AuditQueryResult, AuditQuerySpec, TimeFilter } from \"./query-types.js\";\nimport type { EnrichmentRegistry } from \"./enrichment-registry.js\";\n\n/** Flat console-friendly query filters. Single-value fields translated to multi-value internal filters. */\nexport interface ConsoleQueryFilters {\n tableName?: string;\n operation?: string;\n actorId?: string;\n severity?: string;\n compliance?: string;\n since?: Date;\n until?: Date;\n search?: string;\n limit?: number;\n cursor?: string;\n}\n\n/** Serializable summary of an enrichment config (function fields stripped). */\nexport interface EnrichmentSummary {\n table: string;\n operation: string;\n label?: string;\n severity?: AuditSeverity;\n compliance?: string[];\n notify?: boolean;\n redact?: string[];\n include?: string[];\n}\n\n/** Query result extended with a convenience `hasNextPage` flag. */\nexport interface ConsoleQueryResult extends AuditQueryResult {\n hasNextPage: boolean;\n}\n\n/**\n * High-level API consumed by console endpoints.\n *\n * Wraps the low-level `AuditDatabaseAdapter` methods with sensible defaults\n * (e.g. clamping `limit` to the configured maximum).\n */\nexport interface AuditApi {\n /** Query audit log entries with optional flat filters and cursor-based pagination. */\n queryLogs(filters?: ConsoleQueryFilters): Promise<ConsoleQueryResult>;\n /** Retrieve a single audit log entry by its ID. Returns `null` when not found. */\n getLog(id: string): Promise<AuditLog | null>;\n /** Get aggregated audit statistics. Requires adapter.getStats. */\n getStats(options?: { since?: Date }): Promise<AuditStats>;\n /** Get serializable summaries of all registered enrichments. */\n getEnrichments(): EnrichmentSummary[];\n /** Export logs as CSV or JSON string. */\n exportLogs(filters?: ConsoleQueryFilters, format?: \"csv\" | \"json\"): Promise<string>;\n /** Purge audit logs before a given date. Requires adapter.purgeLogs. */\n purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }>;\n}\n\nconst CSV_HEADERS = [\n \"id\",\n \"timestamp\",\n \"tableName\",\n \"operation\",\n \"recordId\",\n \"actorId\",\n \"severity\",\n \"label\",\n \"description\",\n] as const;\n\nfunction escapeCsvField(value: string): string {\n if (value.includes('\"') || value.includes(\",\") || value.includes(\"\\n\") || value.includes(\"\\r\")) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\nfunction logToCsvRow(log: AuditLog): string {\n const fields = [\n log.id,\n log.timestamp instanceof Date ? log.timestamp.toISOString() : String(log.timestamp),\n log.tableName,\n log.operation,\n log.recordId,\n log.actorId ?? \"\",\n log.severity ?? \"\",\n log.label ?? \"\",\n log.description ?? \"\",\n ];\n return fields.map(escapeCsvField).join(\",\");\n}\n\nfunction toTimeFilter(date: Date): TimeFilter {\n return { date };\n}\n\nfunction buildQuerySpec(filters: ConsoleQueryFilters, effectiveLimit: number): AuditQuerySpec {\n const limit = Math.min(filters.limit ?? effectiveLimit, effectiveLimit);\n\n const spec: AuditQuerySpec = {\n filters: {},\n limit,\n };\n\n if (filters.tableName !== undefined) {\n spec.filters.resource = { tableName: filters.tableName };\n }\n if (filters.actorId !== undefined) {\n spec.filters.actorIds = [filters.actorId];\n }\n if (filters.severity !== undefined) {\n spec.filters.severities = [filters.severity as AuditSeverity];\n }\n if (filters.compliance !== undefined) {\n spec.filters.compliance = [filters.compliance];\n }\n if (filters.operation !== undefined) {\n spec.filters.operations = [filters.operation.toUpperCase() as \"INSERT\" | \"UPDATE\" | \"DELETE\"];\n }\n if (filters.since !== undefined) {\n spec.filters.since = toTimeFilter(filters.since);\n }\n if (filters.until !== undefined) {\n spec.filters.until = toTimeFilter(filters.until);\n }\n if (filters.search !== undefined) {\n spec.filters.searchText = filters.search;\n }\n if (filters.cursor !== undefined) {\n spec.cursor = filters.cursor;\n }\n\n return spec;\n}\n\nexport function createAuditApi(\n adapter: AuditDatabaseAdapter,\n registry: EnrichmentRegistry,\n maxQueryLimit?: number,\n): AuditApi {\n const effectiveLimit = maxQueryLimit ?? 1000;\n\n function requireQueryLogs(): NonNullable<AuditDatabaseAdapter[\"queryLogs\"]> {\n if (adapter.queryLogs === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n return adapter.queryLogs;\n }\n\n function requireGetLogById(): NonNullable<AuditDatabaseAdapter[\"getLogById\"]> {\n if (adapter.getLogById === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements getLogById(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n return adapter.getLogById;\n }\n\n function requireGetStats(): NonNullable<AuditDatabaseAdapter[\"getStats\"]> {\n if (adapter.getStats === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements getStats(). \" +\n \"Check that your ORM adapter supports statistics.\",\n );\n }\n return adapter.getStats;\n }\n\n function requirePurgeLogs(): NonNullable<AuditDatabaseAdapter[\"purgeLogs\"]> {\n if (adapter.purgeLogs === undefined) {\n throw new Error(\n \"Console API requires a database adapter that implements purgeLogs(). \" +\n \"Check that your ORM adapter supports log purging.\",\n );\n }\n return adapter.purgeLogs;\n }\n\n async function queryLogs(filters?: ConsoleQueryFilters): Promise<ConsoleQueryResult> {\n const queryFn = requireQueryLogs();\n const spec = buildQuerySpec(filters ?? {}, effectiveLimit);\n const result = await queryFn(spec);\n return {\n entries: result.entries,\n ...(result.nextCursor !== undefined && { nextCursor: result.nextCursor }),\n hasNextPage: result.nextCursor !== undefined,\n };\n }\n\n async function getLog(id: string): Promise<AuditLog | null> {\n const getLogFn = requireGetLogById();\n return getLogFn(id);\n }\n\n async function getStats(options?: { since?: Date }): Promise<AuditStats> {\n const getStatsFn = requireGetStats();\n return getStatsFn(options);\n }\n\n function getEnrichments(): EnrichmentSummary[] {\n const entries = registry.getEntries();\n const summaries: EnrichmentSummary[] = [];\n\n for (const entry of entries) {\n for (const config of entry.configs) {\n const summary: EnrichmentSummary = {\n table: entry.table,\n operation: entry.operation,\n };\n if (config.label !== undefined) {\n summary.label = config.label;\n }\n if (config.severity !== undefined) {\n summary.severity = config.severity;\n }\n if (config.compliance !== undefined) {\n summary.compliance = config.compliance;\n }\n if (config.notify !== undefined) {\n summary.notify = config.notify;\n }\n if (config.redact !== undefined) {\n summary.redact = config.redact;\n }\n if (config.include !== undefined) {\n summary.include = config.include;\n }\n summaries.push(summary);\n }\n }\n\n return summaries;\n }\n\n async function exportLogs(filters?: ConsoleQueryFilters, format?: \"csv\" | \"json\"): Promise<string> {\n const exportFormat = format ?? \"json\";\n const exportFilters: ConsoleQueryFilters = { ...filters, limit: effectiveLimit };\n const result = await queryLogs(exportFilters);\n\n if (exportFormat === \"json\") {\n return JSON.stringify(result.entries);\n }\n\n const rows = [CSV_HEADERS.join(\",\")];\n for (const log of result.entries) {\n rows.push(logToCsvRow(log));\n }\n return rows.join(\"\\n\");\n }\n\n async function purgeLogs(options: { before: Date; tableName?: string }): Promise<{ deletedCount: number }> {\n const purgeFn = requirePurgeLogs();\n return purgeFn(options);\n }\n\n return { queryLogs, getLog, getStats, getEnrichments, exportLogs, purgeLogs };\n}\n","/**\n * Shared parse and validation utilities for console endpoint handlers.\n *\n * These are small, pure, zero-dep helpers used by product endpoint handlers\n * (audit, tenant) to validate and parse incoming request data.\n */\n\n/** Type guard that narrows `unknown` to a plain object (not array, not null). */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse a string to an integer within [min, max].\n * Returns `undefined` when `value` is `undefined` (param absent),\n * not a finite number, or below `min`.\n */\nexport function parseBoundedInt(\n value: string | undefined,\n min: number,\n max: number,\n): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed < min) {\n return undefined;\n }\n return Math.min(Math.floor(parsed), max);\n}\n\n/**\n * Parse an ISO-8601 date string.\n * Returns `undefined` when `value` is `undefined` or not a valid date.\n */\nexport function parseIsoDate(value: string | undefined): Date | undefined {\n if (value === undefined) {\n return undefined;\n }\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return undefined;\n }\n return date;\n}\n","import type {\n ConsoleProductEndpoint,\n ConsoleProductRequest,\n ConsoleErrorResponse,\n AuditLogWire,\n AuditLogsResponse,\n AuditStatsResponse,\n AuditEnrichmentsResponse,\n AuditPurgeResponse,\n} from \"@usebetterdev/console-contract\";\nimport {\n isPlainObject,\n parseBoundedInt,\n parseIsoDate,\n} from \"@usebetterdev/console-utils\";\nimport type { AuditApi, ConsoleQueryFilters } from \"./audit-api.js\";\nimport type { AuditLog } from \"./types.js\";\n\nconst MAX_PARAM_LENGTH = 1000;\n\nfunction exceedsMaxLength(value: string | undefined): boolean {\n return value !== undefined && value.length > MAX_PARAM_LENGTH;\n}\n\nfunction hasLongQueryParam(query: Record<string, string>): boolean {\n return (\n exceedsMaxLength(query.tableName) ||\n exceedsMaxLength(query.actorId) ||\n exceedsMaxLength(query.cursor) ||\n exceedsMaxLength(query.operation) ||\n exceedsMaxLength(query.severity) ||\n exceedsMaxLength(query.compliance) ||\n exceedsMaxLength(query.search)\n );\n}\n\nfunction parseConsoleQueryFilters(\n query: Record<string, string>,\n): { filters: ConsoleQueryFilters } | { error: string } {\n const filters: ConsoleQueryFilters = {};\n\n if (query.limit !== undefined) {\n const limit = parseBoundedInt(query.limit, 1, 1000);\n if (limit === undefined) {\n return { error: \"Invalid 'limit': must be a positive integer (max 1000)\" };\n }\n filters.limit = limit;\n }\n if (query.tableName !== undefined) {\n filters.tableName = query.tableName;\n }\n if (query.operation !== undefined) {\n filters.operation = query.operation;\n }\n if (query.actorId !== undefined) {\n filters.actorId = query.actorId;\n }\n if (query.severity !== undefined) {\n filters.severity = query.severity;\n }\n if (query.compliance !== undefined) {\n filters.compliance = query.compliance;\n }\n if (query.search !== undefined) {\n filters.search = query.search;\n }\n if (query.cursor !== undefined) {\n filters.cursor = query.cursor;\n }\n\n if (query.since !== undefined) {\n const since = parseIsoDate(query.since);\n if (since === undefined) {\n return { error: \"Invalid 'since': must be an ISO-8601 date\" };\n }\n filters.since = since;\n }\n if (query.until !== undefined) {\n const until = parseIsoDate(query.until);\n if (until === undefined) {\n return { error: \"Invalid 'until': must be an ISO-8601 date\" };\n }\n filters.until = until;\n }\n\n return { filters };\n}\n\nfunction serializeLog(log: AuditLog): AuditLogWire {\n return {\n ...log,\n timestamp: log.timestamp.toISOString(),\n };\n}\n\nexport function createAuditConsoleEndpoints(\n api: AuditApi,\n): ConsoleProductEndpoint[] {\n return [\n {\n method: \"GET\",\n path: \"/logs\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n if (hasLongQueryParam(request.query)) {\n return { status: 400, body: { error: \"Query parameter exceeds maximum length\" } satisfies ConsoleErrorResponse };\n }\n const parsed = parseConsoleQueryFilters(request.query);\n if (\"error\" in parsed) {\n return { status: 400, body: { error: parsed.error } satisfies ConsoleErrorResponse };\n }\n const result = await api.queryLogs(parsed.filters);\n const body: AuditLogsResponse = {\n entries: result.entries.map(serializeLog),\n hasNextPage: result.hasNextPage,\n };\n if (result.nextCursor !== undefined) {\n body.nextCursor = result.nextCursor;\n }\n return { status: 200, body };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/logs/:id\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const id = request.params.id?.trim();\n if (!id) {\n return { status: 400, body: { error: \"Missing log id\" } satisfies ConsoleErrorResponse };\n }\n const log = await api.getLog(id);\n if (!log) {\n return { status: 404, body: { error: \"Audit log not found\" } satisfies ConsoleErrorResponse };\n }\n return { status: 200, body: serializeLog(log) satisfies AuditLogWire };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/stats\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const options: { since?: Date } = {};\n if (request.query.since !== undefined) {\n const since = parseIsoDate(request.query.since);\n if (since === undefined) {\n return { status: 400, body: { error: \"Invalid 'since': must be an ISO-8601 date\" } satisfies ConsoleErrorResponse };\n }\n options.since = since;\n }\n const stats = await api.getStats(options);\n return { status: 200, body: stats satisfies AuditStatsResponse };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/enrichments\",\n requiredPermission: \"read\",\n async handler() {\n try {\n const enrichments = api.getEnrichments();\n return { status: 200, body: enrichments satisfies AuditEnrichmentsResponse };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"GET\",\n path: \"/export\",\n requiredPermission: \"read\",\n async handler(request: ConsoleProductRequest) {\n try {\n const format = request.query.format;\n if (format !== undefined && format !== \"csv\" && format !== \"json\") {\n return { status: 400, body: { error: \"Invalid format. Must be 'csv' or 'json'\" } satisfies ConsoleErrorResponse };\n }\n if (hasLongQueryParam(request.query)) {\n return { status: 400, body: { error: \"Query parameter exceeds maximum length\" } satisfies ConsoleErrorResponse };\n }\n const parsed = parseConsoleQueryFilters(request.query);\n if (\"error\" in parsed) {\n return { status: 400, body: { error: parsed.error } satisfies ConsoleErrorResponse };\n }\n const exportFormat: \"csv\" | \"json\" | undefined = format;\n const data = await api.exportLogs(parsed.filters, exportFormat);\n return { status: 200, body: data };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n {\n method: \"DELETE\",\n path: \"/logs\",\n requiredPermission: \"admin\",\n async handler(request: ConsoleProductRequest) {\n try {\n if (!isPlainObject(request.body)) {\n return { status: 400, body: { error: \"Request body is required\" } satisfies ConsoleErrorResponse };\n }\n const beforeValue = request.body.before;\n if (typeof beforeValue !== \"string\") {\n return { status: 400, body: { error: \"Missing required field: before\" } satisfies ConsoleErrorResponse };\n }\n const before = new Date(beforeValue);\n if (Number.isNaN(before.getTime())) {\n return { status: 400, body: { error: \"Invalid date for 'before' field\" } satisfies ConsoleErrorResponse };\n }\n const options: { before: Date; tableName?: string } = { before };\n if (typeof request.body.tableName === \"string\" && request.body.tableName.length > 0) {\n if (exceedsMaxLength(request.body.tableName)) {\n return { status: 400, body: { error: \"tableName exceeds maximum length\" } satisfies ConsoleErrorResponse };\n }\n options.tableName = request.body.tableName;\n }\n const result = await api.purgeLogs(options);\n return { status: 200, body: result satisfies AuditPurgeResponse };\n } catch {\n return { status: 500, body: { error: \"Internal server error\" } satisfies ConsoleErrorResponse };\n }\n },\n },\n ];\n}\n","import type {\n AfterLogHook,\n AuditContext,\n AuditLog,\n AuditOperation,\n BeforeLogHook,\n BetterAuditConfig,\n BetterAuditInstance,\n CaptureLogInput,\n EnrichmentConfig,\n} from \"./types.js\";\nimport { getAuditContext, runWithAuditContext } from \"./context.js\";\nimport { computeDiff } from \"./diff.js\";\nimport { EnrichmentRegistry, applyEnrichment } from \"./enrichment-registry.js\";\nimport { normalizeInput } from \"./normalize.js\";\nimport { AuditQueryBuilder } from \"./query-builder.js\";\nimport { createAuditApi } from \"./audit-api.js\";\nimport { createAuditConsoleEndpoints } from \"./console-endpoints.js\";\n\nfunction withContext<T>(\n context: AuditContext,\n fn: () => Promise<T>,\n): Promise<T> {\n return runWithAuditContext(context, fn);\n}\n\nexport function betterAudit(config: BetterAuditConfig): BetterAuditInstance {\n const { database } = config;\n const auditTables = new Set(config.auditTables);\n const registry = new EnrichmentRegistry();\n const beforeLogHooks: BeforeLogHook[] = config.beforeLog !== undefined ? [...config.beforeLog] : [];\n const afterLogHooks: AfterLogHook[] = config.afterLog !== undefined ? [...config.afterLog] : [];\n\n function enrich(\n table: string,\n operation: AuditOperation | \"*\",\n enrichmentConfig: EnrichmentConfig,\n ): void {\n if (table !== \"*\" && !auditTables.has(table)) {\n throw new Error(\n `Cannot register enrichment for table \"${table}\": it is not in auditTables. ` +\n `Registered tables: ${[...auditTables].join(\", \")}`,\n );\n }\n\n registry.register(table, operation, enrichmentConfig);\n }\n\n function onBeforeLog(hook: BeforeLogHook): () => void {\n beforeLogHooks.push(hook);\n return () => {\n const index = beforeLogHooks.indexOf(hook);\n if (index !== -1) {\n beforeLogHooks.splice(index, 1);\n }\n };\n }\n\n function onAfterLog(hook: AfterLogHook): () => void {\n afterLogHooks.push(hook);\n return () => {\n const index = afterLogHooks.indexOf(hook);\n if (index !== -1) {\n afterLogHooks.splice(index, 1);\n }\n };\n }\n\n async function writeAndRunAfterHooks(log: AuditLog): Promise<void> {\n await database.writeLog(log);\n for (const hook of afterLogHooks) {\n await hook(log);\n }\n }\n\n async function captureLog(input: CaptureLogInput): Promise<void> {\n // 1. Early return if table not in auditTables\n if (!auditTables.has(input.tableName)) {\n return;\n }\n\n if (input.recordId === \"\") {\n throw new Error(\"captureLog requires a non-empty recordId\");\n }\n\n // 2. Normalize input based on operation type\n const normalized = normalizeInput(input.operation, input.before, input.after);\n\n const context = getAuditContext();\n\n // 3. Assemble log (merge input + AuditContext)\n // Conditional spreads satisfy exactOptionalPropertyTypes — properties\n // are either absent or carry a narrowed non-undefined value.\n const actorId = input.actorId ?? context?.actorId;\n const label = input.label ?? context?.label;\n const reason = input.reason ?? context?.reason;\n const compliance = input.compliance ?? context?.compliance;\n const metadata = input.metadata ?? context?.metadata;\n\n const log: AuditLog = {\n id: crypto.randomUUID(),\n timestamp: new Date(),\n tableName: input.tableName,\n operation: input.operation,\n recordId: input.recordId,\n ...(actorId !== undefined && { actorId }),\n ...(label !== undefined && { label }),\n ...(reason !== undefined && { reason }),\n ...(compliance !== undefined && { compliance }),\n ...(metadata !== undefined && { metadata }),\n ...(input.description !== undefined && { description: input.description }),\n ...(input.severity !== undefined && { severity: input.severity }),\n ...(input.notify !== undefined && { notify: input.notify }),\n ...(normalized.before !== undefined && { beforeData: { ...normalized.before } }),\n ...(normalized.after !== undefined && { afterData: { ...normalized.after } }),\n };\n\n // 4. Compute diff only for UPDATE operations\n if (input.operation === \"UPDATE\") {\n const diff = computeDiff(normalized.before, normalized.after);\n if (diff.changedFields.length > 0) {\n log.diff = diff;\n }\n }\n\n // 5. Resolve enrichment → Redact → Describe → Apply scalars\n const resolved = registry.resolve(input.tableName, input.operation);\n if (resolved !== undefined) {\n applyEnrichment(log, resolved);\n }\n\n // 6. Run beforeLog hooks (sequential, may mutate log, errors abort)\n for (const hook of beforeLogHooks) {\n await hook(log);\n }\n\n // 7. Write log (sync or async) + afterLog hooks\n const isAsync = input.asyncWrite ?? config.asyncWrite ?? false;\n if (isAsync) {\n void writeAndRunAfterHooks(log).catch((error: unknown) => {\n if (config.onError !== undefined) {\n config.onError(error);\n } else {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`audit: async write failed for ${log.tableName}/${log.id} — ${message}`);\n }\n });\n } else {\n await writeAndRunAfterHooks(log);\n }\n }\n\n function query(): AuditQueryBuilder {\n if (database.queryLogs === undefined) {\n throw new Error(\n \"audit.query() requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n const queryLogs = database.queryLogs;\n return new AuditQueryBuilder(\n (spec) => queryLogs(spec),\n undefined,\n undefined,\n undefined,\n config.maxQueryLimit,\n );\n }\n\n if (config.console) {\n const api = createAuditApi(database, registry, config.maxQueryLimit);\n const endpoints = createAuditConsoleEndpoints(api);\n config.console.registerProduct({\n id: \"audit\",\n name: \"Better Audit\",\n endpoints,\n });\n }\n\n return { captureLog, query, withContext, enrich, onBeforeLog, onAfterLog };\n}\n","/**\n * Logical schema for the `audit_logs` table.\n *\n * ORM adapters translate this into their own migration format.\n * Core never runs SQL — this is a declarative data structure only.\n */\n\nexport type ColumnType =\n | \"uuid\"\n | \"timestamptz\"\n | \"text\"\n | \"jsonb\"\n | \"boolean\";\n\nexport interface ColumnDefinition {\n type: ColumnType;\n nullable: boolean;\n primaryKey?: boolean;\n defaultExpression?: string;\n indexed?: boolean;\n description: string;\n}\n\n/**\n * Column names in the `audit_logs` table use snake_case.\n * These map to camelCase properties in the `AuditLog` TypeScript type:\n *\n * | Column (snake_case) | AuditLog property (camelCase) |\n * |---------------------|-------------------------------|\n * | table_name | tableName |\n * | record_id | recordId |\n * | actor_id | actorId |\n * | before_data | beforeData |\n * | after_data | afterData |\n * | redacted_fields | redactedFields |\n */\nexport type AuditLogColumnName =\n | \"id\"\n | \"timestamp\"\n | \"table_name\"\n | \"operation\"\n | \"record_id\"\n | \"actor_id\"\n | \"before_data\"\n | \"after_data\"\n | \"diff\"\n | \"label\"\n | \"description\"\n | \"severity\"\n | \"compliance\"\n | \"notify\"\n | \"reason\"\n | \"metadata\"\n | \"redacted_fields\";\n\nexport interface AuditLogSchema {\n tableName: string;\n columns: Record<string, ColumnDefinition>;\n}\n\nexport const AUDIT_LOG_SCHEMA: AuditLogSchema = {\n tableName: \"audit_logs\",\n columns: {\n id: {\n type: \"uuid\",\n nullable: false,\n primaryKey: true,\n defaultExpression: \"gen_random_uuid()\",\n description: \"Unique identifier for the audit log entry\",\n },\n timestamp: {\n type: \"timestamptz\",\n nullable: false,\n defaultExpression: \"now()\",\n indexed: true,\n description: \"When the audited event occurred\",\n },\n table_name: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Name of the table that was modified\",\n },\n operation: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Type of operation: INSERT, UPDATE, or DELETE\",\n },\n record_id: {\n type: \"text\",\n nullable: false,\n indexed: true,\n description: \"Primary key of the affected record\",\n },\n actor_id: {\n type: \"text\",\n nullable: true,\n indexed: true,\n description: \"Identifier of the user or system that performed the action\",\n },\n before_data: {\n type: \"jsonb\",\n nullable: true,\n description: \"Row snapshot before the mutation (DELETE and UPDATE only)\",\n },\n after_data: {\n type: \"jsonb\",\n nullable: true,\n description: \"Row snapshot after the mutation (INSERT and UPDATE only)\",\n },\n diff: {\n type: \"jsonb\",\n nullable: true,\n description: \"Changed field names for UPDATE operations\",\n },\n label: {\n type: \"text\",\n nullable: true,\n description: \"Human-readable label for the event\",\n },\n description: {\n type: \"text\",\n nullable: true,\n description: \"Detailed description of what happened\",\n },\n severity: {\n type: \"text\",\n nullable: true,\n description: \"Severity level: low, medium, high, or critical\",\n },\n compliance: {\n type: \"jsonb\",\n nullable: true,\n description: \"Compliance framework tags (e.g. soc2, gdpr, hipaa)\",\n },\n notify: {\n type: \"boolean\",\n nullable: true,\n description: \"Whether this event should trigger notifications\",\n },\n reason: {\n type: \"text\",\n nullable: true,\n description: \"Justification or reason for the action\",\n },\n metadata: {\n type: \"jsonb\",\n nullable: true,\n description: \"Arbitrary key-value metadata attached to the event\",\n },\n redacted_fields: {\n type: \"jsonb\",\n nullable: true,\n description: \"Field names that were removed by redaction rules\",\n },\n },\n};\n","import type { AuditContext } from \"./types.js\";\nimport { runWithAuditContext } from \"./context.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Function that extracts a string value from a Web Request.\n * Return undefined if the value cannot be extracted.\n * May be sync or async.\n */\nexport type ValueExtractor = (\n request: Request,\n) => string | undefined | Promise<string | undefined>;\n\n/**\n * Describes how to extract audit identity from an incoming HTTP request.\n * All fields are optional — if omitted, the corresponding context field\n * will be undefined.\n */\nexport interface ContextExtractor {\n /** Extracts the actor identifier (user / service account). */\n actor?: ValueExtractor;\n}\n\n/**\n * Options for `handleMiddleware`.\n */\nexport interface MiddlewareHandlerOptions {\n /** Called when an extractor throws. Defaults to silent fail-open. */\n onError?: (error: unknown) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Built-in extractors\n// ---------------------------------------------------------------------------\n\n/**\n * Decode a JWT payload without verification.\n * Uses only Web-standard APIs (atob) — safe on edge runtimes.\n */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split(\".\");\n if (parts.length < 2) {\n return null;\n }\n const payload = parts[1];\n if (!payload) {\n return null;\n }\n let base64 = payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (base64.length % 4 !== 0) {\n base64 += \"=\";\n }\n const decoded = atob(base64);\n const parsed: unknown = JSON.parse(decoded);\n if (typeof parsed !== \"object\" || parsed === null) {\n return null;\n }\n return parsed as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Extracts a JWT claim from the `Authorization: Bearer <token>` header.\n *\n * Decodes the token **without** signature verification — signing is the\n * auth layer's responsibility. This is intentional: the audit layer only\n * needs the identity, not proof of authenticity.\n *\n * @param claim - JWT claim name to extract (e.g. `\"sub\"`, `\"tenant_id\"`)\n */\nexport function fromBearerToken(claim: string): ValueExtractor {\n return (request: Request) => {\n const authorization = request.headers.get(\"authorization\");\n if (!authorization) {\n return undefined;\n }\n const parts = authorization.split(\" \");\n if (parts.length !== 2 || parts[0]?.toLowerCase() !== \"bearer\") {\n return undefined;\n }\n const token = parts[1];\n if (!token) {\n return undefined;\n }\n const payload = decodeJwtPayload(token);\n if (!payload) {\n return undefined;\n }\n const value = payload[claim];\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n };\n}\n\n/**\n * Extracts a value from a named cookie in the `Cookie` header.\n *\n * The raw cookie value is returned as-is. To resolve it into a user identity,\n * compose with a resolver function (e.g. look up a session in a database).\n *\n * @param cookieName - Name of the cookie to read\n */\nexport function fromCookie(cookieName: string): ValueExtractor {\n return (request: Request) => {\n const cookieHeader = request.headers.get(\"cookie\");\n if (!cookieHeader) {\n return undefined;\n }\n const cookies = cookieHeader.split(\";\");\n for (const cookie of cookies) {\n const separatorIndex = cookie.indexOf(\"=\");\n if (separatorIndex === -1) {\n continue;\n }\n const name = cookie.slice(0, separatorIndex).trim();\n if (name === cookieName) {\n const value = cookie.slice(separatorIndex + 1).trim();\n return value.length > 0 ? value : undefined;\n }\n }\n return undefined;\n };\n}\n\n/**\n * Extracts a value from a custom request header.\n *\n * @param headerName - Header name (case-insensitive per the Web API)\n */\nexport function fromHeader(headerName: string): ValueExtractor {\n return (request: Request) => {\n const value = request.headers.get(headerName);\n if (!value || value.trim().length === 0) {\n return undefined;\n }\n return value.trim();\n };\n}\n\n// ---------------------------------------------------------------------------\n// Shared middleware handler\n// ---------------------------------------------------------------------------\n\n/**\n * Runs an extractor safely, catching errors and returning undefined on failure.\n */\nasync function safeExtract(\n extractor: ValueExtractor | undefined,\n request: Request,\n onError: ((error: unknown) => void) | undefined,\n): Promise<string | undefined> {\n if (!extractor) {\n return undefined;\n }\n try {\n return await extractor(request);\n } catch (error: unknown) {\n if (onError) {\n onError(error);\n }\n return undefined;\n }\n}\n\n/**\n * Shared middleware handler used by all framework adapters.\n *\n * 1. Extracts actor from the request using the provided extractor\n * 2. Builds an `AuditContext` (undefined if nothing was extracted)\n * 3. Wraps `next()` inside `runWithAuditContext()` if context is available\n * 4. Calls `next()` without context if extraction yields nothing\n *\n * Extraction failures never break the request (fail open).\n */\nexport async function handleMiddleware(\n extractor: ContextExtractor,\n request: Request,\n next: () => Promise<void>,\n options: MiddlewareHandlerOptions = {},\n): Promise<void> {\n const actorId = await safeExtract(extractor.actor, request, options.onError);\n\n if (actorId === undefined) {\n await next();\n return;\n }\n\n const auditContext: AuditContext = { actorId };\n\n await runWithAuditContext(auditContext, () => next());\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebetterdev/audit-core",
3
- "version": "0.4.0-beta.2",
3
+ "version": "0.4.0-beta.4",
4
4
  "repository": "github:usebetter-dev/usebetter",
5
5
  "bugs": "https://github.com/usebetter-dev/usebetter/issues",
6
6
  "homepage": "https://github.com/usebetter-dev/usebetter#readme",
@@ -29,7 +29,8 @@
29
29
  "tsup": "^8.3.5",
30
30
  "typescript": "~5.7.2",
31
31
  "vitest": "^2.1.6",
32
- "@usebetterdev/console-contract": "0.4.0-beta.2"
32
+ "@usebetterdev/console-contract": "0.4.0-beta.4",
33
+ "@usebetterdev/console-utils": "0.4.0-beta.4"
33
34
  },
34
35
  "engines": {
35
36
  "node": ">=22"