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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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","../../../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":[]}
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/export.ts","../src/audit-api.ts","../../../shared/console-utils/src/index.ts","../src/console-endpoints.ts","../src/export-response.ts","../src/retention.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 { AuditLog, ExportOptions, ExportResult } from \"./types.js\";\nimport type { AuditQuerySpec } from \"./query-types.js\";\nimport type { QueryExecutor } from \"./query-builder.js\";\n\nconst DEFAULT_BATCH_SIZE = 500;\nconst DEFAULT_CSV_DELIMITER = \",\";\n\n/**\n * Ordered list of AuditLog fields for CSV columns.\n * Matches the full schema — JSONB columns are JSON.stringify'd.\n */\nconst CSV_COLUMNS: ReadonlyArray<keyof AuditLog> = [\n \"id\",\n \"timestamp\",\n \"tableName\",\n \"operation\",\n \"recordId\",\n \"actorId\",\n \"beforeData\",\n \"afterData\",\n \"diff\",\n \"label\",\n \"description\",\n \"severity\",\n \"compliance\",\n \"notify\",\n \"reason\",\n \"metadata\",\n \"redactedFields\",\n];\n\n/** Fields whose values are objects/arrays and need JSON.stringify in CSV. */\nconst JSON_FIELDS = new Set<keyof AuditLog>([\n \"beforeData\",\n \"afterData\",\n \"diff\",\n \"compliance\",\n \"metadata\",\n \"redactedFields\",\n]);\n\n/**\n * Escape a CSV field per RFC 4180.\n * Fields containing the delimiter, double quotes, or line breaks are quoted.\n * Embedded double quotes are doubled.\n */\nfunction escapeCsvField(value: string, delimiter: string): string {\n if (\n value.includes('\"') ||\n value.includes(delimiter) ||\n value.includes(\"\\n\") ||\n value.includes(\"\\r\")\n ) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n\nfunction formatCsvValue(\n log: AuditLog,\n field: keyof AuditLog,\n delimiter: string,\n): string {\n const value = log[field];\n if (value === undefined || value === null) {\n return \"\";\n }\n if (JSON_FIELDS.has(field)) {\n return escapeCsvField(JSON.stringify(value), delimiter);\n }\n if (value instanceof Date) {\n return escapeCsvField(value.toISOString(), delimiter);\n }\n if (typeof value === \"boolean\") {\n return value ? \"true\" : \"false\";\n }\n return escapeCsvField(String(value), delimiter);\n}\n\n/** Abstraction over WritableStream<string> and in-memory string buffer. */\ninterface ExportSink {\n write(chunk: string): Promise<void>;\n finish(): Promise<string | undefined>;\n abort(error: unknown): Promise<void>;\n}\n\nfunction createStringSink(): ExportSink {\n const chunks: string[] = [];\n return {\n async write(chunk: string): Promise<void> {\n chunks.push(chunk);\n },\n async finish(): Promise<string> {\n return chunks.join(\"\");\n },\n async abort(): Promise<void> {\n // Nothing to clean up for in-memory buffer\n },\n };\n}\n\nfunction createStreamSink(stream: WritableStream<string>): ExportSink {\n const writer = stream.getWriter();\n return {\n async write(chunk: string): Promise<void> {\n await writer.write(chunk);\n },\n async finish(): Promise<undefined> {\n await writer.close();\n return undefined;\n },\n async abort(error: unknown): Promise<void> {\n await writer.abort(error);\n },\n };\n}\n\n/**\n * Core export engine. Fetches rows in cursor-paginated batches and writes\n * them to the sink as CSV or JSON. Memory stays flat regardless of total rows.\n */\nexport async function runExport(\n executor: QueryExecutor,\n options: ExportOptions,\n): Promise<ExportResult> {\n const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n if (batchSize <= 0) {\n throw new Error(`batchSize must be greater than 0, got ${batchSize}`);\n }\n\n const delimiter = options.csvDelimiter ?? DEFAULT_CSV_DELIMITER;\n if (delimiter.length !== 1) {\n throw new Error(\"csvDelimiter must be exactly one character\");\n }\n\n const jsonStyle = options.jsonStyle ?? \"ndjson\";\n\n const sink =\n options.output === \"string\"\n ? createStringSink()\n : createStreamSink(options.output);\n const isStringSink = options.output === \"string\";\n\n // Build initial query spec from the optional query builder\n const baseSpec: AuditQuerySpec = options.query !== undefined\n ? options.query.toSpec()\n : { filters: {} };\n\n // Respect the user's query limit as a total cap; use batchSize for per-page fetching\n const totalLimit = baseSpec.limit;\n const spec: AuditQuerySpec = { ...baseSpec, limit: batchSize };\n\n let rowCount = 0;\n\n try {\n if (options.format === \"csv\") {\n // Write header row\n const header = CSV_COLUMNS.map((col) => escapeCsvField(col, delimiter)).join(delimiter);\n await sink.write(header + \"\\n\");\n\n // Paginate and write rows\n let cursor: string | undefined;\n for (;;) {\n const currentSpec: AuditQuerySpec = cursor !== undefined\n ? { ...spec, cursor }\n : spec;\n const result = await executor(currentSpec);\n\n if (isStringSink) {\n // Batch writes for string sink — one write() per page\n const lines: string[] = [];\n for (const entry of result.entries) {\n if (totalLimit !== undefined && rowCount >= totalLimit) {\n break;\n }\n const row = CSV_COLUMNS.map((col) => formatCsvValue(entry, col, delimiter)).join(delimiter);\n lines.push(row + \"\\n\");\n rowCount++;\n }\n if (lines.length > 0) {\n await sink.write(lines.join(\"\"));\n }\n } else {\n // Stream sink — per-row writes for backpressure\n for (const entry of result.entries) {\n if (totalLimit !== undefined && rowCount >= totalLimit) {\n break;\n }\n const row = CSV_COLUMNS.map((col) => formatCsvValue(entry, col, delimiter)).join(delimiter);\n await sink.write(row + \"\\n\");\n rowCount++;\n }\n }\n\n if (totalLimit !== undefined && rowCount >= totalLimit) {\n break;\n }\n if (result.nextCursor === undefined) {\n break;\n }\n cursor = result.nextCursor;\n }\n } else {\n // JSON format\n if (jsonStyle === \"array\") {\n let cursor: string | undefined;\n const entries: AuditLog[] = [];\n\n for (;;) {\n const currentSpec: AuditQuerySpec = cursor !== undefined\n ? { ...spec, cursor }\n : spec;\n const result = await executor(currentSpec);\n\n for (const entry of result.entries) {\n if (totalLimit !== undefined && rowCount >= totalLimit) {\n break;\n }\n entries.push(entry);\n rowCount++;\n }\n\n if (totalLimit !== undefined && rowCount >= totalLimit) {\n break;\n }\n if (result.nextCursor === undefined) {\n break;\n }\n cursor = result.nextCursor;\n }\n\n await sink.write(JSON.stringify(entries, null, 2) + \"\\n\");\n } else {\n // ndjson\n let cursor: string | undefined;\n for (;;) {\n const currentSpec: AuditQuerySpec = cursor !== undefined\n ? { ...spec, cursor }\n : spec;\n const result = await executor(currentSpec);\n\n if (isStringSink) {\n // Batch writes for string sink — one write() per page\n const lines: string[] = [];\n for (const entry of result.entries) {\n if (totalLimit !== undefined && rowCount >= totalLimit) {\n break;\n }\n lines.push(JSON.stringify(entry) + \"\\n\");\n rowCount++;\n }\n if (lines.length > 0) {\n await sink.write(lines.join(\"\"));\n }\n } else {\n // Stream sink — per-row writes for backpressure\n for (const entry of result.entries) {\n if (totalLimit !== undefined && rowCount >= totalLimit) {\n break;\n }\n await sink.write(JSON.stringify(entry) + \"\\n\");\n rowCount++;\n }\n }\n\n if (totalLimit !== undefined && rowCount >= totalLimit) {\n break;\n }\n if (result.nextCursor === undefined) {\n break;\n }\n cursor = result.nextCursor;\n }\n }\n }\n } catch (error) {\n await sink.abort(error);\n throw error;\n }\n\n const data = await sink.finish();\n\n if (data !== undefined) {\n return { rowCount, data };\n }\n return { rowCount };\n}\n","import type { AuditDatabaseAdapter, AuditLog, AuditStats, AuditSeverity, AuditOperation } from \"./types.js\";\nimport type { AuditQueryResult, AuditQuerySpec, TimeFilter } from \"./query-types.js\";\nimport type { EnrichmentRegistry } from \"./enrichment-registry.js\";\nimport { AuditQueryBuilder } from \"./query-builder.js\";\nimport { runExport } from \"./export.js\";\n\nconst VALID_SEVERITIES = new Set<string>([\"low\", \"medium\", \"high\", \"critical\"]);\nconst VALID_OPERATIONS = new Set<string>([\"INSERT\", \"UPDATE\", \"DELETE\"]);\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\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 if (!VALID_SEVERITIES.has(filters.severity)) {\n throw new Error(\n `Invalid severity \"${filters.severity}\". Must be one of: low, medium, high, critical`,\n );\n }\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 const normalized = filters.operation.toUpperCase();\n if (!VALID_OPERATIONS.has(normalized)) {\n throw new Error(\n `Invalid operation \"${filters.operation}\". Must be one of: INSERT, UPDATE, DELETE`,\n );\n }\n spec.filters.operations = [normalized as AuditOperation];\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 queryFn = requireQueryLogs();\n const exportFormat = format ?? \"json\";\n\n // Build a query spec from the console filters (without cursor — export fetches all pages)\n const spec = buildQuerySpec(filters ?? {}, effectiveLimit);\n\n // Construct a query builder pre-loaded with the console filters\n const queryBuilder = new AuditQueryBuilder(\n (s) => queryFn(s),\n spec.filters,\n spec.limit,\n undefined,\n effectiveLimit,\n spec.sortOrder,\n );\n\n const exportOptions = {\n format: exportFormat,\n query: queryBuilder,\n output: \"string\" as const,\n ...(exportFormat === \"json\" && { jsonStyle: \"array\" as const }),\n };\n\n const result = await runExport(\n (s) => queryFn(s),\n exportOptions,\n );\n return result.data ?? \"\";\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 { AuditQueryBuilder, QueryExecutor } from \"./query-builder.js\";\nimport { runExport } from \"./export.js\";\n\n/** Options for `createExportResponse()`. */\nexport interface ExportResponseOptions {\n /** Output format. Default: `\"csv\"`. */\n format?: \"csv\" | \"json\";\n /** JSON output style. Default: `\"ndjson\"`. */\n jsonStyle?: \"ndjson\" | \"array\";\n /** Rows per database round-trip. */\n batchSize?: number;\n /** CSV delimiter character. */\n csvDelimiter?: string;\n /** Optional query builder to filter exported entries. */\n query?: AuditQueryBuilder;\n /** Custom filename stem (without extension). Default: `\"audit-export-YYYY-MM-DD\"`. */\n filename?: string;\n}\n\nfunction contentTypeForFormat(\n format: \"csv\" | \"json\",\n jsonStyle: \"ndjson\" | \"array\",\n): string {\n if (format === \"csv\") {\n return \"text/csv; charset=utf-8\";\n }\n if (jsonStyle === \"ndjson\") {\n return \"application/x-ndjson; charset=utf-8\";\n }\n return \"application/json; charset=utf-8\";\n}\n\nfunction fileExtensionForFormat(\n format: \"csv\" | \"json\",\n jsonStyle: \"ndjson\" | \"array\",\n): string {\n if (format === \"csv\") {\n return \".csv\";\n }\n if (jsonStyle === \"ndjson\") {\n return \".ndjson\";\n }\n return \".json\";\n}\n\nfunction formatDate(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, \"0\");\n const day = String(date.getDate()).padStart(2, \"0\");\n return `${year}-${month}-${day}`;\n}\n\n/**\n * Sanitise a filename for use in Content-Disposition headers.\n * Strips characters that could break the header or enable response splitting.\n */\nfunction sanitiseFilename(name: string): string {\n return name.replace(/[\"\\\\\\r\\n\\x00-\\x1f]/g, \"\");\n}\n\n/**\n * Create a streaming `Response` from the audit export engine.\n *\n * Uses `TransformStream` + `TextEncoderStream` to bridge the string-based\n * `runExport()` output to a binary `ReadableStream` suitable for `new Response()`.\n * The export runs asynchronously — the Response is returned immediately so the\n * first byte can arrive before all rows are read.\n */\nexport function createExportResponse(\n executor: QueryExecutor,\n options: ExportResponseOptions = {},\n): Response {\n const format = options.format ?? \"csv\";\n const jsonStyle = options.jsonStyle ?? \"ndjson\";\n\n const stem =\n options.filename ?? `audit-export-${formatDate(new Date())}`;\n const extension = fileExtensionForFormat(format, jsonStyle);\n const fullFilename = sanitiseFilename(`${stem}${extension}`);\n\n const contentType = contentTypeForFormat(format, jsonStyle);\n\n // Bridge: runExport writes strings into the writable side of the transform.\n // The readable side feeds into TextEncoderStream, producing Uint8Array for the Response.\n const transform = new TransformStream<string, string>();\n const encoder = new TextEncoderStream();\n const readable = transform.readable.pipeThrough(encoder);\n\n // Start the export asynchronously — do NOT await.\n // runExport closes/aborts the writable side on completion/error.\n // The .catch() prevents unhandled rejection — the error is already\n // propagated to the reader via sink.abort() inside runExport.\n runExport(executor, {\n format,\n jsonStyle,\n output: transform.writable,\n ...(options.batchSize !== undefined && { batchSize: options.batchSize }),\n ...(options.csvDelimiter !== undefined && {\n csvDelimiter: options.csvDelimiter,\n }),\n ...(options.query !== undefined && { query: options.query }),\n }).catch(() => {\n // Error already propagated through the transform stream\n });\n\n return new Response(readable, {\n status: 200,\n headers: {\n \"Content-Type\": contentType,\n \"Content-Disposition\": `attachment; filename=\"${fullFilename}\"`,\n \"Cache-Control\": \"no-cache\",\n },\n });\n}\n","import type { RetentionPolicy } from \"./types.js\";\n\nexport function validateRetentionPolicy(policy: RetentionPolicy): void {\n if (\n !Number.isInteger(policy.days) ||\n !Number.isFinite(policy.days) ||\n policy.days <= 0\n ) {\n throw new Error(\n `retention: 'days' must be a positive integer, got ${String(policy.days)}`,\n );\n }\n\n if (policy.tables !== undefined) {\n if (\n !Array.isArray(policy.tables) ||\n policy.tables.length === 0 ||\n policy.tables.some((t) => typeof t !== \"string\" || t === \"\")\n ) {\n throw new Error(\n \"retention: 'tables' must be a non-empty array of non-empty strings\",\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 ExportOptions,\n ExportResult,\n ExportResponseOptions,\n RetentionPolicy,\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\";\nimport { runExport } from \"./export.js\";\nimport { createExportResponse } from \"./export-response.js\";\nimport { validateRetentionPolicy } from \"./retention.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 if (config.retention !== undefined) {\n validateRetentionPolicy(config.retention);\n }\n\n const { database } = config;\n const auditTables = new Set(config.auditTables);\n\n if (config.retention?.tables !== undefined) {\n const unknown = config.retention.tables.filter((t) => !auditTables.has(t));\n if (unknown.length > 0) {\n throw new Error(\n `retention: 'tables' contains table(s) not in auditTables: ${unknown.join(\", \")}. ` +\n `Registered tables: ${[...auditTables].join(\", \")}`,\n );\n }\n }\n const registry = new EnrichmentRegistry();\n\n const resolvedRetention: RetentionPolicy | undefined =\n config.retention !== undefined\n ? {\n ...config.retention,\n ...(config.retention.tables !== undefined && { tables: [...config.retention.tables] }),\n }\n : undefined;\n\n function retentionPolicy(): RetentionPolicy | undefined {\n if (resolvedRetention === undefined) {\n return undefined;\n }\n return {\n ...resolvedRetention,\n ...(resolvedRetention.tables !== undefined && { tables: [...resolvedRetention.tables] }),\n };\n }\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 async function exportLogs(options: ExportOptions): Promise<ExportResult> {\n if (database.queryLogs === undefined) {\n throw new Error(\n \"audit.export() requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n const queryLogs = database.queryLogs;\n return runExport((spec) => queryLogs(spec), options);\n }\n\n function exportResponse(options?: ExportResponseOptions): Response {\n if (database.queryLogs === undefined) {\n throw new Error(\n \"audit.exportResponse() requires a database adapter that implements queryLogs(). \" +\n \"Check that your ORM adapter supports querying.\",\n );\n }\n const queryLogs = database.queryLogs;\n return createExportResponse((spec) => queryLogs(spec), options);\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, export: exportLogs, exportResponse, withContext, enrich, onBeforeLog, onAfterLog, retentionPolicy };\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.4",
3
+ "version": "0.5.0",
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,14 +29,15 @@
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.4",
33
- "@usebetterdev/console-utils": "0.4.0-beta.4"
32
+ "@usebetterdev/console-contract": "0.5.0",
33
+ "@usebetterdev/console-utils": "0.5.0-beta.1"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=22"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "tsup",
40
+ "build:types": "tsc --build tsconfig.build.json",
40
41
  "test": "vitest run",
41
42
  "typecheck": "tsc --noEmit"
42
43
  }