@richardmcquiston01/error-log-adapter 0.1.0 → 0.3.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/serialize.ts","../src/adapters/console-error-adapter.ts","../src/adapters/webhook-error-adapter.ts","../src/adapters/mongodb-error-adapter.ts","../src/adapters/composite-error-adapter.ts"],"sourcesContent":["import type { ErrorLogEntry } from './types.js';\n\n/** A JSON-safe copy of an `ErrorLogEntry`. */\nexport interface SerializedErrorLogEntry {\n code: string;\n message: string;\n cause?: unknown;\n source?: string;\n timestamp: string;\n}\n\n/**\n * Converts `Error` instances into plain objects, since `JSON.stringify`\n * renders them as `{}`. Other values are returned unchanged.\n */\nexport function serializeCause(cause: unknown): unknown {\n if (cause instanceof Error) {\n return { name: cause.name, message: cause.message, stack: cause.stack };\n }\n return cause;\n}\n\n/** Returns a JSON-safe copy of the entry, adding a timestamp if missing. */\nexport function serializeEntry(entry: ErrorLogEntry): SerializedErrorLogEntry {\n const serialized: SerializedErrorLogEntry = {\n code: entry.code,\n message: entry.message,\n timestamp: entry.timestamp ?? new Date().toISOString(),\n };\n if (entry.cause !== undefined) {\n serialized.cause = serializeCause(entry.cause);\n }\n if (entry.source !== undefined) {\n serialized.source = entry.source;\n }\n return serialized;\n}\n\n/** Turns an unknown thrown value into a descriptive message. */\nexport function describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n","import type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\nexport interface ConsoleErrorOptions {\n /** Text printed before each entry. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\nexport class ConsoleErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly prefix: string;\n\n constructor(options: ConsoleErrorOptions = {}) {\n this.prefix = options.prefix ?? '[error-log-adapter]';\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n console.error(this.prefix, entry);\n return { ok: true };\n } catch (error: unknown) {\n return {\n ok: false,\n error: `ConsoleErrorAdapter failed: ${String(error)}`,\n };\n }\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\nexport interface WebhookErrorOptions {\n /** Extra headers merged into the request. */\n headers?: Record<string, string>;\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\nexport class WebhookErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly prefix: string;\n\n constructor(\n private readonly url: string,\n private readonly options: WebhookErrorOptions = {}\n ) {\n this.prefix = options.prefix ?? '[error-log-adapter]';\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n const response: Response = await fetch(this.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.options.headers,\n },\n body: JSON.stringify(serializeEntry(entry)),\n });\n if (!response.ok) {\n return this.fail(\n `webhook ${this.url} responded with HTTP ${response.status}`\n );\n }\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} WebhookErrorAdapter:`, error);\n return this.fail(\n `could not reach webhook ${this.url}: ${describeError(error)}`\n );\n }\n }\n\n private fail(reason: string): ErrorLogResult {\n return { ok: false, error: `WebhookErrorAdapter failed: ${reason}` };\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** The subset of a MongoDB `Collection` this adapter uses. */\nexport interface CollectionLike {\n insertOne(doc: object): Promise<unknown>;\n}\n\nexport interface MongoDbErrorOptions {\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\nexport class MongoDbErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly prefix: string;\n\n constructor(\n private readonly collection: CollectionLike,\n options: MongoDbErrorOptions = {}\n ) {\n this.prefix = options.prefix ?? '[error-log-adapter]';\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n await this.collection.insertOne(serializeEntry(entry));\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} MongoDbErrorAdapter:`, error);\n return {\n ok: false,\n error: `MongoDbErrorAdapter failed to insert entry: ${describeError(error)}`,\n };\n }\n }\n}\n","import type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Sends every entry to several adapters. Succeeds only if all of them do. */\nexport class CompositeErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n constructor(\n private readonly adapters: ReadonlyArray<ErrorLogAdapter<TCode>>\n ) {}\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n const results: ErrorLogResult[] = await Promise.all(\n this.adapters.map(\n async (adapter: ErrorLogAdapter<TCode>): Promise<ErrorLogResult> => {\n try {\n return await adapter.log(entry);\n } catch (error: unknown) {\n // Adapters should not throw, but a faulty custom one must not break the others.\n return {\n ok: false,\n error: `adapter threw instead of returning a result: ${String(error)}`,\n };\n }\n }\n )\n );\n const failures: string[] = results.flatMap(\n (result: ErrorLogResult): string[] =>\n result.ok ? [] : [result.error ?? 'unknown failure']\n );\n if (failures.length === 0) {\n return { ok: true };\n }\n return {\n ok: false,\n error: `${failures.length} of ${results.length} adapters failed: ${failures.join('; ')}`,\n };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/serialize.ts","../src/adapters/console-error-adapter.ts","../src/adapters/webhook-error-adapter.ts","../src/adapters/mongodb-error-adapter.ts","../src/adapters/composite-error-adapter.ts","../src/adapters/indexeddb-error-adapter.ts","../src/adapters/sqlite-error-adapter.ts","../src/adapters/web-storage-error-adapter.ts","../src/file-system.ts","../src/adapters/flat-file-error-adapter.ts","../src/adapters/json-file-error-adapter.ts","../src/adapters/sql-error-adapter.ts","../src/adapters/redis-error-adapter.ts","../src/adapters/s3-error-adapter.ts"],"sourcesContent":["import type { ErrorLogEntry } from './types.js';\n\n/** A JSON-safe copy of an `ErrorLogEntry`. */\nexport interface SerializedErrorLogEntry {\n code: string;\n message: string;\n cause?: unknown;\n source?: string;\n timestamp: string;\n}\n\n/**\n * Converts `Error` instances into plain objects, since `JSON.stringify`\n * renders them as `{}`. Other values are returned unchanged.\n */\nexport function serializeCause(cause: unknown): unknown {\n if (cause instanceof Error) {\n return { name: cause.name, message: cause.message, stack: cause.stack };\n }\n return cause;\n}\n\n/** Returns a JSON-safe copy of the entry, adding a timestamp if missing. */\nexport function serializeEntry(entry: ErrorLogEntry): SerializedErrorLogEntry {\n const serialized: SerializedErrorLogEntry = {\n code: entry.code,\n message: entry.message,\n timestamp: entry.timestamp ?? new Date().toISOString(),\n };\n if (entry.cause !== undefined) {\n serialized.cause = serializeCause(entry.cause);\n }\n if (entry.source !== undefined) {\n serialized.source = entry.source;\n }\n return serialized;\n}\n\n/** Turns an unknown thrown value into a descriptive message. */\nexport function describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n","import type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\nexport interface ConsoleErrorOptions {\n /** Text printed before each entry. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\nexport class ConsoleErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly prefix: string;\n\n constructor(options: ConsoleErrorOptions = {}) {\n this.prefix = options.prefix ?? '[error-log-adapter]';\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n console.error(this.prefix, entry);\n return { ok: true };\n } catch (error: unknown) {\n return {\n ok: false,\n error: `ConsoleErrorAdapter failed: ${String(error)}`,\n };\n }\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\nexport interface WebhookErrorOptions {\n /** Extra headers merged into the request. */\n headers?: Record<string, string>;\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\nexport class WebhookErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly prefix: string;\n\n constructor(\n private readonly url: string,\n private readonly options: WebhookErrorOptions = {}\n ) {\n this.prefix = options.prefix ?? '[error-log-adapter]';\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n const response: Response = await fetch(this.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.options.headers,\n },\n body: JSON.stringify(serializeEntry(entry)),\n });\n if (!response.ok) {\n return this.fail(\n `webhook ${this.url} responded with HTTP ${response.status}`\n );\n }\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} WebhookErrorAdapter:`, error);\n return this.fail(\n `could not reach webhook ${this.url}: ${describeError(error)}`\n );\n }\n }\n\n private fail(reason: string): ErrorLogResult {\n return { ok: false, error: `WebhookErrorAdapter failed: ${reason}` };\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** The subset of a MongoDB `Collection` this adapter uses. */\nexport interface CollectionLike {\n insertOne(doc: object): Promise<unknown>;\n}\n\nexport interface MongoDbErrorOptions {\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\nexport class MongoDbErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly prefix: string;\n\n constructor(\n private readonly collection: CollectionLike,\n options: MongoDbErrorOptions = {}\n ) {\n this.prefix = options.prefix ?? '[error-log-adapter]';\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n await this.collection.insertOne(serializeEntry(entry));\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} MongoDbErrorAdapter:`, error);\n return {\n ok: false,\n error: `MongoDbErrorAdapter failed to insert entry: ${describeError(error)}`,\n };\n }\n }\n}\n","import type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Sends every entry to several adapters. Succeeds only if all of them do. */\nexport class CompositeErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n constructor(\n private readonly adapters: ReadonlyArray<ErrorLogAdapter<TCode>>\n ) {}\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n const results: ErrorLogResult[] = await Promise.all(\n this.adapters.map(\n async (adapter: ErrorLogAdapter<TCode>): Promise<ErrorLogResult> => {\n try {\n return await adapter.log(entry);\n } catch (error: unknown) {\n // Adapters should not throw, but a faulty custom one must not break the others.\n return {\n ok: false,\n error: `adapter threw instead of returning a result: ${String(error)}`,\n };\n }\n }\n )\n );\n const failures: string[] = results.flatMap(\n (result: ErrorLogResult): string[] =>\n result.ok ? [] : [result.error ?? 'unknown failure']\n );\n if (failures.length === 0) {\n return { ok: true };\n }\n return {\n ok: false,\n error: `${failures.length} of ${results.length} adapters failed: ${failures.join('; ')}`,\n };\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type { SerializedErrorLogEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Default object store name. */\nexport const DEFAULT_STORE_NAME = 'error_log';\n\n/** Event handler slot. Typed loosely so real DOM `IDBTransaction` objects fit. */\ntype IdbHandler = ((event: never) => unknown) | null;\n\n/** The subset of an `IDBObjectStore` this adapter uses. */\nexport interface IdbObjectStoreLike {\n add(value: unknown): unknown;\n}\n\n/** The subset of an `IDBTransaction` this adapter uses. */\nexport interface IdbTransactionLike {\n objectStore(name: string): IdbObjectStoreLike;\n oncomplete: IdbHandler;\n onerror: IdbHandler;\n onabort: IdbHandler;\n readonly error?: { message?: string } | null;\n}\n\n/** The subset of an `IDBDatabase` this adapter uses. */\nexport interface IdbDatabaseLike {\n transaction(storeNames: string, mode: 'readwrite'): IdbTransactionLike;\n}\n\n/** The subset of an `IDBDatabase` available inside `onupgradeneeded`. */\nexport interface IdbUpgradeDatabaseLike {\n readonly objectStoreNames: { contains(name: string): boolean };\n createObjectStore(\n name: string,\n options: { keyPath: string }\n ): { createIndex(name: string, keyPath: string): unknown };\n}\n\n/** A stored record: the serialized entry plus its generated UUID key. */\nexport interface IndexedDbErrorRecord extends SerializedErrorLogEntry {\n id: string;\n}\n\nexport interface IndexedDbErrorOptions {\n /** Object store to write to. Defaults to `error_log`. */\n storeName?: string;\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\n/**\n * Creates the error log object store (UUID `id` key, `timestamp` index).\n * Call it from your `onupgradeneeded` handler. Does nothing if it already exists.\n */\nexport function createErrorLogStore(\n database: IdbUpgradeDatabaseLike,\n storeName: string = DEFAULT_STORE_NAME\n): void {\n if (database.objectStoreNames.contains(storeName)) {\n return;\n }\n database\n .createObjectStore(storeName, { keyPath: 'id' })\n .createIndex('timestamp', 'timestamp');\n}\n\nexport class IndexedDbErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly storeName: string;\n private readonly prefix: string;\n\n constructor(\n private readonly database: IdbDatabaseLike,\n options: IndexedDbErrorOptions = {}\n ) {\n this.storeName = options.storeName ?? DEFAULT_STORE_NAME;\n this.prefix = options.prefix ?? '[error-log-adapter]';\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n const record: IndexedDbErrorRecord = {\n id: crypto.randomUUID(),\n ...serializeEntry(entry),\n };\n await this.write(record);\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} IndexedDbErrorAdapter:`, error);\n return {\n ok: false,\n error: `IndexedDbErrorAdapter failed to write to object store \"${this.storeName}\": ${describeError(error)}`,\n };\n }\n }\n\n /** Resolves once the transaction commits; rejects if it errors or aborts. */\n private write(record: IndexedDbErrorRecord): Promise<void> {\n return new Promise<void>(\n (resolve: () => void, reject: (reason: unknown) => void): void => {\n const transaction: IdbTransactionLike = this.database.transaction(\n this.storeName,\n 'readwrite'\n );\n const fail = (action: string) => (): void =>\n reject(\n new Error(\n `transaction ${action}: ${transaction.error?.message ?? 'no details provided'}`\n )\n );\n transaction.oncomplete = (): void => resolve();\n transaction.onerror = fail('failed');\n transaction.onabort = fail('aborted');\n transaction.objectStore(this.storeName).add(record);\n }\n );\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Default table name. */\nexport const DEFAULT_TABLE_NAME = 'error_log';\n\nconst IDENTIFIER_PATTERN: RegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * The only thing this adapter needs from a SQLite driver: run one statement\n * with positional (`?`) parameters. `bun:sqlite` already matches this shape.\n * Other drivers need a one-line wrapper, for example:\n *\n * ```ts\n * // better-sqlite3 or node:sqlite\n * const client: SqliteClientLike = {\n * run: (sql, params) => db.prepare(sql).run(...params),\n * };\n * ```\n */\nexport interface SqliteClientLike {\n run(sql: string, params: unknown[]): unknown;\n}\n\nexport interface SqliteErrorOptions {\n /** Table to write to. Defaults to `error_log`. Letters, digits and `_` only. */\n tableName?: string;\n /** Create the table (and index) on first use. Defaults to `true`. */\n createTable?: boolean;\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\n/** Throws a descriptive error unless `tableName` is a plain SQL identifier. */\nfunction assertValidTableName(tableName: string): void {\n if (!IDENTIFIER_PATTERN.test(tableName)) {\n throw new RangeError(\n `SqliteErrorAdapter: invalid table name \"${tableName}\". Use only letters, digits and underscores, and do not start with a digit.`\n );\n }\n}\n\n/**\n * Returns the statements that create the error log table and its index.\n * Use this to run your own migration when `createTable` is disabled.\n */\nexport function buildCreateErrorLogTableSql(\n tableName: string = DEFAULT_TABLE_NAME\n): string[] {\n assertValidTableName(tableName);\n return [\n `CREATE TABLE IF NOT EXISTS ${tableName} (` +\n 'id TEXT PRIMARY KEY NOT NULL, ' +\n 'code TEXT NOT NULL, ' +\n 'message TEXT NOT NULL, ' +\n 'cause TEXT, ' +\n 'source TEXT, ' +\n 'occurred_at TEXT NOT NULL, ' +\n \"created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))\" +\n ')',\n `CREATE INDEX IF NOT EXISTS ${tableName}_occurred_at_idx ON ${tableName} (occurred_at)`,\n ];\n}\n\nexport class SqliteErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly tableName: string;\n private readonly createTable: boolean;\n private readonly prefix: string;\n private tableReady: Promise<void> | null = null;\n\n /** @throws {RangeError} if `options.tableName` is not a plain identifier. */\n constructor(\n private readonly client: SqliteClientLike,\n options: SqliteErrorOptions = {}\n ) {\n this.tableName = options.tableName ?? DEFAULT_TABLE_NAME;\n assertValidTableName(this.tableName);\n this.createTable = options.createTable ?? true;\n this.prefix = options.prefix ?? '[error-log-adapter]';\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n await this.ensureTable();\n const serialized = serializeEntry(entry);\n await this.client.run(\n `INSERT INTO ${this.tableName} (id, code, message, cause, source, occurred_at) VALUES (?, ?, ?, ?, ?, ?)`,\n [\n crypto.randomUUID(),\n serialized.code,\n serialized.message,\n serialized.cause === undefined\n ? null\n : JSON.stringify(serialized.cause),\n serialized.source ?? null,\n serialized.timestamp,\n ]\n );\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} SqliteErrorAdapter:`, error);\n return {\n ok: false,\n error: `SqliteErrorAdapter failed to write to table \"${this.tableName}\": ${describeError(error)}`,\n };\n }\n }\n\n /** Creates the table once. A failed attempt is retried on the next log call. */\n private ensureTable(): Promise<void> {\n if (!this.createTable) {\n return Promise.resolve();\n }\n if (this.tableReady === null) {\n this.tableReady = this.runCreateStatements().catch(\n (error: unknown): never => {\n this.tableReady = null;\n throw error;\n }\n );\n }\n return this.tableReady;\n }\n\n private async runCreateStatements(): Promise<void> {\n for (const sql of buildCreateErrorLogTableSql(this.tableName)) {\n await this.client.run(sql, []);\n }\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type { SerializedErrorLogEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Default storage key. */\nexport const DEFAULT_STORAGE_KEY = 'error_log';\n\n/** Default number of entries kept before the oldest are dropped. */\nexport const DEFAULT_MAX_ENTRIES = 100;\n\n/**\n * The subset of the Web Storage API this adapter uses. Both `sessionStorage`\n * and `localStorage` fit.\n */\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n}\n\n/** A stored record: the serialized entry plus its generated UUID. */\nexport interface WebStorageErrorRecord extends SerializedErrorLogEntry {\n id: string;\n}\n\nexport interface WebStorageErrorOptions {\n /** Key the entries are stored under, as one JSON array. Defaults to `error_log`. */\n key?: string;\n /** Most entries to keep. The oldest are dropped first. Defaults to `100`. */\n maxEntries?: number;\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\n/** Browsers report a full store with different names and codes. */\nfunction isQuotaError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) {\n return false;\n }\n const { name, code } = error as { name?: unknown; code?: unknown };\n return (\n (typeof name === 'string' && name.toLowerCase().includes('quota')) ||\n code === 22 ||\n code === 1014\n );\n}\n\n/**\n * Keeps the newest entries in a Web Storage object (`sessionStorage` or\n * `localStorage`) as a JSON array under one key, capped at `maxEntries`.\n * When the store is full, the oldest entries are dropped until the new one fits.\n */\nexport class WebStorageErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly key: string;\n private readonly maxEntries: number;\n private readonly prefix: string;\n\n /** @throws {RangeError} if `options.maxEntries` is not a positive integer. */\n constructor(\n private readonly storage: StorageLike,\n options: WebStorageErrorOptions = {}\n ) {\n this.key = options.key ?? DEFAULT_STORAGE_KEY;\n this.maxEntries = options.maxEntries ?? DEFAULT_MAX_ENTRIES;\n this.prefix = options.prefix ?? '[error-log-adapter]';\n if (!Number.isInteger(this.maxEntries) || this.maxEntries < 1) {\n throw new RangeError(\n `WebStorageErrorAdapter: maxEntries must be a positive integer, received ${String(options.maxEntries)}.`\n );\n }\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n const record: WebStorageErrorRecord = {\n id: crypto.randomUUID(),\n ...serializeEntry(entry),\n };\n this.append(record);\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} WebStorageErrorAdapter:`, error);\n return {\n ok: false,\n error: `WebStorageErrorAdapter failed to write to storage key \"${this.key}\": ${describeError(error)}`,\n };\n }\n }\n\n /** Reads, appends, trims, and writes back. Runs without awaiting, so calls never interleave. */\n private append(record: WebStorageErrorRecord): void {\n const records: WebStorageErrorRecord[] = [\n ...this.readRecords(),\n record,\n ].slice(-this.maxEntries);\n for (;;) {\n try {\n this.storage.setItem(this.key, JSON.stringify(records));\n return;\n } catch (error: unknown) {\n if (!isQuotaError(error) || records.length <= 1) {\n throw error;\n }\n records.shift();\n }\n }\n }\n\n /** Returns the stored records, or none if the value is missing or unreadable. */\n private readRecords(): WebStorageErrorRecord[] {\n const stored: string | null = this.storage.getItem(this.key);\n if (stored === null) {\n return [];\n }\n try {\n const parsed: unknown = JSON.parse(stored);\n if (Array.isArray(parsed)) {\n return parsed as WebStorageErrorRecord[];\n }\n console.error(\n `${this.prefix} WebStorageErrorAdapter: storage key \"${this.key}\" did not hold an array, so it was replaced.`\n );\n } catch (error: unknown) {\n console.error(\n `${this.prefix} WebStorageErrorAdapter: storage key \"${this.key}\" held invalid JSON (${describeError(error)}), so it was replaced.`\n );\n }\n return [];\n }\n}\n","/**\n * The subset of `node:fs/promises` the file adapters use. Pass `fs.promises`\n * (or `node:fs/promises`) yourself, or leave `fs` unset in Node and Bun.\n */\nexport interface FileSystemLike {\n appendFile(path: string, data: string): Promise<unknown>;\n mkdir(path: string, options: { recursive: true }): Promise<unknown>;\n stat(path: string): Promise<{ size: number }>;\n rename(from: string, to: string): Promise<unknown>;\n rm(path: string, options: { force: true }): Promise<unknown>;\n}\n\n/** `FileSystemLike` plus the reading and replacing that whole-file writes need. */\nexport interface ReadWriteFileSystemLike extends FileSystemLike {\n readFile(path: string, encoding: 'utf8'): Promise<string>;\n writeFile(path: string, data: string): Promise<unknown>;\n}\n\n/** Loads `node:fs/promises`. Only call this from code that runs in Node or Bun. */\nexport async function loadNodeFileSystem(): Promise<ReadWriteFileSystemLike> {\n // A variable specifier keeps browser bundlers from resolving it.\n const moduleName: string = 'node:fs/promises';\n return (await import(moduleName)) as ReadWriteFileSystemLike;\n}\n\n/** Returns the folder part of a path, or `null` if the path has none. */\nexport function parentDirectory(filePath: string): string | null {\n const lastSeparator: number = Math.max(\n filePath.lastIndexOf('/'),\n filePath.lastIndexOf('\\\\')\n );\n if (lastSeparator < 0) {\n return null;\n }\n return lastSeparator === 0 ? '/' : filePath.slice(0, lastSeparator);\n}\n\n/** True for the error Node throws when a file or folder does not exist. */\nexport function isMissingFileError(error: unknown): boolean {\n return (\n typeof error === 'object' &&\n error !== null &&\n (error as { code?: unknown }).code === 'ENOENT'\n );\n}\n","import {\n isMissingFileError,\n loadNodeFileSystem,\n parentDirectory,\n} from '../file-system.js';\nimport type { FileSystemLike } from '../file-system.js';\nimport { describeError, serializeEntry } from '../serialize.js';\nimport type { SerializedErrorLogEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Default number of rotated files kept when `maxBytes` is set. */\nexport const DEFAULT_MAX_FILES = 3;\n\nexport interface FlatFileErrorOptions {\n /** File system to write to. Defaults to `node:fs/promises`, loaded on first use. */\n fs?: FileSystemLike;\n /** Turns an entry into one line of text. Defaults to `formatErrorLogLine`. */\n formatLine?: (entry: SerializedErrorLogEntry) => string;\n /** Rotate the file before it would grow past this many bytes. Off by default. */\n maxBytes?: number;\n /** Rotated files to keep (`app.log.1` is newest). Defaults to `3`. Needs `maxBytes`. */\n maxFiles?: number;\n /** Create missing parent directories on first use. Defaults to `true`. */\n createDirectory?: boolean;\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\n/** Keeps one entry on one line by escaping line breaks. */\nfunction escapeLineBreaks(text: string): string {\n return text.replace(/\\r?\\n/g, '\\\\n');\n}\n\nfunction describeCause(cause: unknown): string {\n if (typeof cause === 'string') {\n return cause;\n }\n if (typeof cause === 'object' && cause !== null) {\n const { name, message } = cause as { name?: unknown; message?: unknown };\n if (typeof name === 'string' && typeof message === 'string') {\n return `${name}: ${message}`;\n }\n }\n try {\n return JSON.stringify(cause) ?? String(cause);\n } catch {\n return String(cause);\n }\n}\n\n/**\n * The default line format:\n * `2026-01-02T03:04:05.000Z [CODE] source: message (cause: Error: details)`.\n * `source` and `cause` are left out when the entry has none.\n */\nexport function formatErrorLogLine(entry: SerializedErrorLogEntry): string {\n const source: string = entry.source === undefined ? '' : `${entry.source}: `;\n const cause: string =\n entry.cause === undefined ? '' : ` (cause: ${describeCause(entry.cause)})`;\n return escapeLineBreaks(\n `${entry.timestamp} [${entry.code}] ${source}${entry.message}${cause}`\n );\n}\n\nfunction assertPositiveInteger(value: number, name: string): void {\n if (!Number.isInteger(value) || value < 1) {\n throw new RangeError(\n `FlatFileErrorAdapter: ${name} must be a positive integer, received ${String(value)}.`\n );\n }\n}\n\n/**\n * Appends one text line per entry to a file, optionally rotating it by size.\n * Writes are queued, so concurrent `log` calls never interleave or race a rotation.\n */\nexport class FlatFileErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly formatLine: (entry: SerializedErrorLogEntry) => string;\n private readonly maxBytes: number | undefined;\n private readonly maxFiles: number;\n private readonly createDirectory: boolean;\n private readonly prefix: string;\n private fs: FileSystemLike | undefined;\n private directoryReady: boolean = false;\n private queue: Promise<unknown> = Promise.resolve();\n\n /** @throws {RangeError} if `maxBytes` or `maxFiles` is not a positive integer. */\n constructor(\n private readonly filePath: string,\n options: FlatFileErrorOptions = {}\n ) {\n this.fs = options.fs;\n this.formatLine = options.formatLine ?? formatErrorLogLine;\n this.maxBytes = options.maxBytes;\n this.maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;\n this.createDirectory = options.createDirectory ?? true;\n this.prefix = options.prefix ?? '[error-log-adapter]';\n if (this.maxBytes !== undefined) {\n assertPositiveInteger(this.maxBytes, 'maxBytes');\n }\n assertPositiveInteger(this.maxFiles, 'maxFiles');\n }\n\n log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n const result: Promise<ErrorLogResult> = this.queue.then(() =>\n this.write(entry)\n );\n // write() never rejects, but keep the queue alive no matter what.\n this.queue = result.catch(() => undefined);\n return result;\n }\n\n private async write(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n const fs: FileSystemLike = await this.getFileSystem();\n const formatted: string = this.formatLine(serializeEntry(entry));\n const line: string = formatted.endsWith('\\n')\n ? formatted\n : `${formatted}\\n`;\n await this.prepareDirectory(fs);\n await this.rotateIfNeeded(fs, new TextEncoder().encode(line).length);\n await fs.appendFile(this.filePath, line);\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} FlatFileErrorAdapter:`, error);\n return {\n ok: false,\n error: `FlatFileErrorAdapter failed to append to \"${this.filePath}\": ${describeError(error)}`,\n };\n }\n }\n\n /** Uses the injected file system, or loads `node:fs/promises` on first use. */\n private async getFileSystem(): Promise<FileSystemLike> {\n if (this.fs === undefined) {\n this.fs = await loadNodeFileSystem();\n }\n return this.fs;\n }\n\n private async prepareDirectory(fs: FileSystemLike): Promise<void> {\n if (!this.createDirectory || this.directoryReady) {\n return;\n }\n const directory: string | null = parentDirectory(this.filePath);\n if (directory !== null) {\n await fs.mkdir(directory, { recursive: true });\n }\n this.directoryReady = true;\n }\n\n /**\n * Moves `app.log` to `app.log.1` (shifting older files up, dropping the\n * oldest) when the next line would push it past `maxBytes`. A failed rotation\n * is reported, but the entry is still written so it is not lost.\n */\n private async rotateIfNeeded(\n fs: FileSystemLike,\n lineBytes: number\n ): Promise<void> {\n if (this.maxBytes === undefined) {\n return;\n }\n try {\n let currentSize: number;\n try {\n currentSize = (await fs.stat(this.filePath)).size;\n } catch (error: unknown) {\n if (isMissingFileError(error)) {\n return;\n }\n throw error;\n }\n if (currentSize === 0 || currentSize + lineBytes <= this.maxBytes) {\n return;\n }\n await fs.rm(`${this.filePath}.${this.maxFiles}`, { force: true });\n for (let index = this.maxFiles - 1; index >= 1; index -= 1) {\n await this.renameIfPresent(\n fs,\n `${this.filePath}.${index}`,\n `${this.filePath}.${index + 1}`\n );\n }\n await fs.rename(this.filePath, `${this.filePath}.1`);\n } catch (error: unknown) {\n console.error(\n `${this.prefix} FlatFileErrorAdapter: could not rotate \"${this.filePath}\", so it will keep growing: ${describeError(error)}`\n );\n }\n }\n\n private async renameIfPresent(\n fs: FileSystemLike,\n from: string,\n to: string\n ): Promise<void> {\n try {\n await fs.rename(from, to);\n } catch (error: unknown) {\n if (!isMissingFileError(error)) {\n throw error;\n }\n }\n }\n}\n","import {\n isMissingFileError,\n loadNodeFileSystem,\n parentDirectory,\n} from '../file-system.js';\nimport type { ReadWriteFileSystemLike } from '../file-system.js';\nimport { describeError, serializeEntry } from '../serialize.js';\nimport type { SerializedErrorLogEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Default number of spaces used to indent array mode output. */\nexport const DEFAULT_INDENT = 2;\n\n/** A stored record: the serialized entry plus its generated UUID. */\nexport interface JsonFileErrorRecord extends SerializedErrorLogEntry {\n id: string;\n}\n\nexport interface JsonFileErrorOptions {\n /**\n * `ndjson` (default) appends one compact JSON object per line, which is cheap\n * and safe to tail. `array` keeps the whole file as one JSON array that is\n * rewritten on every entry.\n */\n mode?: 'ndjson' | 'array';\n /** Array mode only: most entries to keep. The oldest are dropped first. */\n maxEntries?: number;\n /** Array mode only: spaces of indentation. Defaults to `2`; `0` is compact. */\n indent?: number;\n /** File system to write to. Defaults to `node:fs/promises`, loaded on first use. */\n fs?: ReadWriteFileSystemLike;\n /** Create missing parent directories on first use. Defaults to `true`. */\n createDirectory?: boolean;\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\n/**\n * Stores entries as JSON in a file (Node and Bun), either as newline-delimited\n * JSON or as one array. Writes are queued, so concurrent `log` calls stay in\n * order. Only one process should write to a given file.\n */\nexport class JsonFileErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly mode: 'ndjson' | 'array';\n private readonly maxEntries: number | undefined;\n private readonly indent: number;\n private readonly createDirectory: boolean;\n private readonly prefix: string;\n private fs: ReadWriteFileSystemLike | undefined;\n private directoryReady: boolean = false;\n private queue: Promise<unknown> = Promise.resolve();\n\n /** @throws {RangeError} if `maxEntries` or `indent` is invalid, or `maxEntries` is used outside array mode. */\n constructor(\n private readonly filePath: string,\n options: JsonFileErrorOptions = {}\n ) {\n this.mode = options.mode ?? 'ndjson';\n this.maxEntries = options.maxEntries;\n this.indent = options.indent ?? DEFAULT_INDENT;\n this.fs = options.fs;\n this.createDirectory = options.createDirectory ?? true;\n this.prefix = options.prefix ?? '[error-log-adapter]';\n if (this.maxEntries !== undefined) {\n if (this.mode !== 'array') {\n throw new RangeError(\n `JsonFileErrorAdapter: maxEntries only applies to mode \"array\", but mode is \"${this.mode}\". Use mode \"array\" or remove maxEntries.`\n );\n }\n if (!Number.isInteger(this.maxEntries) || this.maxEntries < 1) {\n throw new RangeError(\n `JsonFileErrorAdapter: maxEntries must be a positive integer, received ${String(this.maxEntries)}.`\n );\n }\n }\n if (!Number.isInteger(this.indent) || this.indent < 0) {\n throw new RangeError(\n `JsonFileErrorAdapter: indent must be a non-negative integer, received ${String(this.indent)}.`\n );\n }\n }\n\n log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n const result: Promise<ErrorLogResult> = this.queue.then(() =>\n this.write(entry)\n );\n // write() never rejects, but keep the queue alive no matter what.\n this.queue = result.catch(() => undefined);\n return result;\n }\n\n private async write(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n const record: JsonFileErrorRecord = {\n id: crypto.randomUUID(),\n ...serializeEntry(entry),\n };\n const fs: ReadWriteFileSystemLike = await this.getFileSystem();\n await this.prepareDirectory(fs);\n if (this.mode === 'ndjson') {\n await fs.appendFile(this.filePath, `${JSON.stringify(record)}\\n`);\n } else {\n await this.rewriteArray(fs, record);\n }\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} JsonFileErrorAdapter:`, error);\n return {\n ok: false,\n error: `JsonFileErrorAdapter failed to write \"${this.filePath}\" in ${this.mode} mode: ${describeError(error)}`,\n };\n }\n }\n\n private async getFileSystem(): Promise<ReadWriteFileSystemLike> {\n if (this.fs === undefined) {\n this.fs = await loadNodeFileSystem();\n }\n return this.fs;\n }\n\n private async prepareDirectory(fs: ReadWriteFileSystemLike): Promise<void> {\n if (!this.createDirectory || this.directoryReady) {\n return;\n }\n const directory: string | null = parentDirectory(this.filePath);\n if (directory !== null) {\n await fs.mkdir(directory, { recursive: true });\n }\n this.directoryReady = true;\n }\n\n /**\n * Adds the record to the array and replaces the file. The new content is\n * written to a temporary file first and renamed into place, so a crash\n * mid-write cannot leave a half-written file.\n */\n private async rewriteArray(\n fs: ReadWriteFileSystemLike,\n record: JsonFileErrorRecord\n ): Promise<void> {\n const records: JsonFileErrorRecord[] = [\n ...(await this.readRecords(fs)),\n record,\n ];\n const kept: JsonFileErrorRecord[] =\n this.maxEntries === undefined ? records : records.slice(-this.maxEntries);\n const temporaryPath: string = `${this.filePath}.tmp`;\n await fs.writeFile(\n temporaryPath,\n `${JSON.stringify(kept, null, this.indent)}\\n`\n );\n await fs.rename(temporaryPath, this.filePath);\n }\n\n /**\n * Returns the records already in the file. A missing or empty file has none.\n * A file that is not a JSON array is moved to `<path>.corrupt` (so nothing is\n * lost) and logging continues with a fresh file.\n */\n private async readRecords(\n fs: ReadWriteFileSystemLike\n ): Promise<JsonFileErrorRecord[]> {\n let content: string;\n try {\n content = await fs.readFile(this.filePath, 'utf8');\n } catch (error: unknown) {\n if (isMissingFileError(error)) {\n return [];\n }\n throw error;\n }\n if (content.trim() === '') {\n return [];\n }\n let problem: string;\n try {\n const parsed: unknown = JSON.parse(content);\n if (Array.isArray(parsed)) {\n return parsed as JsonFileErrorRecord[];\n }\n problem = 'did not hold a JSON array';\n } catch (error: unknown) {\n problem = `held invalid JSON (${describeError(error)})`;\n }\n const corruptPath: string = `${this.filePath}.corrupt`;\n await fs.rename(this.filePath, corruptPath);\n console.error(\n `${this.prefix} JsonFileErrorAdapter: \"${this.filePath}\" ${problem}, so it was moved to \"${corruptPath}\" and a new file was started.`\n );\n return [];\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Default table name. */\nexport const DEFAULT_TABLE_NAME = 'error_log';\n\n/**\n * Longest allowed table name. The index name adds `_occurred_at_idx` (16\n * characters) and Postgres limits identifiers to 63.\n */\nexport const MAX_TABLE_NAME_LENGTH = 47;\n\nconst IDENTIFIER_PATTERN: RegExp = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport type SqlDialect = 'postgres' | 'mysql';\n\n/**\n * The only thing these adapters need from a database driver: run one\n * parameterized statement. `pg` (`Pool` / `Client`) and `mysql2/promise`\n * (`Pool` / `Connection`) both fit as they are.\n */\nexport interface SqlClientLike {\n query(sql: string, params: unknown[]): Promise<unknown>;\n}\n\nexport interface SqlErrorOptions {\n /** Table to write to. Defaults to `error_log`. Letters, digits and `_` only, up to 47 characters. */\n tableName?: string;\n /** Create the table (and index) on first use. Defaults to `true`. */\n createTable?: boolean;\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\n/** Throws a descriptive error unless `tableName` is a short, plain SQL identifier. */\nfunction assertValidTableName(tableName: string, adapterName: string): void {\n if (!IDENTIFIER_PATTERN.test(tableName)) {\n throw new RangeError(\n `${adapterName}: invalid table name \"${tableName}\". Use only letters, digits and underscores, and do not start with a digit.`\n );\n }\n if (tableName.length > MAX_TABLE_NAME_LENGTH) {\n throw new RangeError(\n `${adapterName}: table name \"${tableName}\" is ${tableName.length} characters long, but the limit is ${MAX_TABLE_NAME_LENGTH}.`\n );\n }\n}\n\nfunction quoteIdentifier(dialect: SqlDialect, identifier: string): string {\n return dialect === 'postgres' ? `\"${identifier}\"` : `\\`${identifier}\\``;\n}\n\n/**\n * Returns the statements that create the error log table and its index for a\n * dialect. Use this to run your own migration when `createTable` is disabled.\n *\n * Postgres gets a native `UUID` key, `JSONB` for the cause, and `TIMESTAMPTZ`\n * columns. MySQL gets a `CHAR(36)` UUID key (it has no UUID type), a `JSON`\n * cause, and `DATETIME(3)` columns holding UTC times.\n */\nexport function buildErrorLogTableDdl(\n dialect: SqlDialect,\n tableName: string = DEFAULT_TABLE_NAME\n): string[] {\n assertValidTableName(tableName, 'buildErrorLogTableDdl');\n const table: string = quoteIdentifier(dialect, tableName);\n const index: string = quoteIdentifier(\n dialect,\n `${tableName}_occurred_at_idx`\n );\n if (dialect === 'postgres') {\n return [\n `CREATE TABLE IF NOT EXISTS ${table} (` +\n 'id UUID PRIMARY KEY, ' +\n 'code TEXT NOT NULL, ' +\n 'message TEXT NOT NULL, ' +\n 'cause JSONB, ' +\n 'source TEXT, ' +\n 'occurred_at TIMESTAMPTZ NOT NULL, ' +\n 'created_at TIMESTAMPTZ NOT NULL DEFAULT now()' +\n ')',\n `CREATE INDEX IF NOT EXISTS ${index} ON ${table} (occurred_at)`,\n ];\n }\n // MySQL has no CREATE INDEX IF NOT EXISTS, so the index is part of the table.\n return [\n `CREATE TABLE IF NOT EXISTS ${table} (` +\n 'id CHAR(36) NOT NULL PRIMARY KEY, ' +\n 'code VARCHAR(255) NOT NULL, ' +\n 'message TEXT NOT NULL, ' +\n 'cause JSON NULL, ' +\n 'source VARCHAR(255) NULL, ' +\n 'occurred_at DATETIME(3) NOT NULL, ' +\n 'created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), ' +\n `INDEX ${index} (occurred_at)` +\n ')',\n ];\n}\n\n/** `2026-01-02T03:04:05.678Z` becomes `2026-01-02 03:04:05.678`, which MySQL accepts. */\nfunction toMysqlDateTime(date: Date): string {\n return date.toISOString().replace('T', ' ').replace('Z', '');\n}\n\n/** Shared by the Postgres and MySQL adapters: only the SQL text and time format differ. */\nabstract class SqlErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly tableName: string;\n private readonly createTable: boolean;\n private readonly prefix: string;\n private tableReady: Promise<void> | null = null;\n\n protected constructor(\n private readonly client: SqlClientLike,\n private readonly dialect: SqlDialect,\n private readonly adapterName: string,\n options: SqlErrorOptions\n ) {\n this.tableName = options.tableName ?? DEFAULT_TABLE_NAME;\n assertValidTableName(this.tableName, adapterName);\n this.createTable = options.createTable ?? true;\n this.prefix = options.prefix ?? '[error-log-adapter]';\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n const serialized = serializeEntry(entry);\n const occurredAt: Date = new Date(serialized.timestamp);\n if (Number.isNaN(occurredAt.getTime())) {\n throw new RangeError(\n `timestamp \"${serialized.timestamp}\" is not a valid ISO 8601 date`\n );\n }\n const params: unknown[] = [\n crypto.randomUUID(),\n serialized.code,\n serialized.message,\n serialized.cause === undefined\n ? null\n : JSON.stringify(serialized.cause),\n serialized.source ?? null,\n this.dialect === 'postgres'\n ? occurredAt.toISOString()\n : toMysqlDateTime(occurredAt),\n ];\n await this.ensureTable();\n await this.client.query(this.insertSql(), params);\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} ${this.adapterName}:`, error);\n return {\n ok: false,\n error: `${this.adapterName} failed to write to table \"${this.tableName}\": ${describeError(error)}`,\n };\n }\n }\n\n private insertSql(): string {\n const placeholders: string =\n this.dialect === 'postgres'\n ? '$1, $2, $3, $4, $5, $6'\n : '?, ?, ?, ?, ?, ?';\n return `INSERT INTO ${quoteIdentifier(this.dialect, this.tableName)} (id, code, message, cause, source, occurred_at) VALUES (${placeholders})`;\n }\n\n /** Creates the table once. A failed attempt is retried on the next log call. */\n private ensureTable(): Promise<void> {\n if (!this.createTable) {\n return Promise.resolve();\n }\n if (this.tableReady === null) {\n this.tableReady = this.runCreateStatements().catch(\n (error: unknown): never => {\n this.tableReady = null;\n throw error;\n }\n );\n }\n return this.tableReady;\n }\n\n private async runCreateStatements(): Promise<void> {\n for (const sql of buildErrorLogTableDdl(this.dialect, this.tableName)) {\n await this.client.query(sql, []);\n }\n }\n}\n\n/**\n * Writes entries to Postgres. Pass a `pg` `Pool` or `Client`.\n *\n * ```ts\n * const adapter = new PostgresErrorAdapter(new Pool({ connectionString }));\n * ```\n */\nexport class PostgresErrorAdapter<\n TCode extends string = string,\n> extends SqlErrorAdapter<TCode> {\n /** @throws {RangeError} if `options.tableName` is not a plain identifier of 47 characters or fewer. */\n constructor(client: SqlClientLike, options: SqlErrorOptions = {}) {\n super(client, 'postgres', 'PostgresErrorAdapter', options);\n }\n}\n\n/**\n * Writes entries to MySQL or MariaDB. Pass a `mysql2/promise` `Pool` or `Connection`.\n *\n * ```ts\n * const adapter = new MySqlErrorAdapter(mysql.createPool(connectionUri));\n * ```\n */\nexport class MySqlErrorAdapter<\n TCode extends string = string,\n> extends SqlErrorAdapter<TCode> {\n /** @throws {RangeError} if `options.tableName` is not a plain identifier of 47 characters or fewer. */\n constructor(client: SqlClientLike, options: SqlErrorOptions = {}) {\n super(client, 'mysql', 'MySqlErrorAdapter', options);\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Default Redis key. */\nexport const DEFAULT_REDIS_KEY = 'error_log';\n\n/**\n * The only thing this adapter needs from a Redis client: send one raw command\n * as an array of strings. node-redis (`client.sendCommand`) already matches.\n * ioredis needs a one-line wrapper:\n *\n * ```ts\n * const client: RedisClientLike = {\n * sendCommand: (args) => redis.call(args[0] as string, ...args.slice(1)),\n * };\n * ```\n */\nexport interface RedisClientLike {\n sendCommand(args: string[]): Promise<unknown>;\n}\n\nexport interface RedisErrorOptions {\n /**\n * `list` (default) pushes each entry onto a Redis list as one JSON string.\n * `stream` appends each entry to a Redis stream (`XADD`) as separate fields.\n */\n mode?: 'list' | 'stream';\n /** Key to write to. Defaults to `error_log`. */\n key?: string;\n /**\n * Most entries to keep. The oldest are dropped first. Unlimited by default.\n * Lists are trimmed with `LTRIM` after the push; streams use an exact `MAXLEN`.\n */\n maxEntries?: number;\n /**\n * List mode only: which end to push onto. `right` (default, `RPUSH`) keeps\n * the newest entry last; `left` (`LPUSH`) keeps the newest entry first.\n */\n side?: 'left' | 'right';\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\n/**\n * Writes entries to Redis, either as a capped list of JSON strings or as a\n * stream. Each record holds a UUID `id` plus the serialized entry. In stream\n * mode `cause` and `source` fields are left out when the entry has none.\n */\nexport class RedisErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly mode: 'list' | 'stream';\n private readonly key: string;\n private readonly maxEntries: number | undefined;\n private readonly side: 'left' | 'right';\n private readonly prefix: string;\n\n /** @throws {RangeError} if `key` is empty, `maxEntries` is not a positive integer, or `mode` / `side` is unknown. */\n constructor(\n private readonly client: RedisClientLike,\n options: RedisErrorOptions = {}\n ) {\n this.mode = options.mode ?? 'list';\n this.key = options.key ?? DEFAULT_REDIS_KEY;\n this.maxEntries = options.maxEntries;\n this.side = options.side ?? 'right';\n this.prefix = options.prefix ?? '[error-log-adapter]';\n if (this.mode !== 'list' && this.mode !== 'stream') {\n throw new RangeError(\n `RedisErrorAdapter: mode must be \"list\" or \"stream\", received \"${String(options.mode)}\".`\n );\n }\n if (this.side !== 'left' && this.side !== 'right') {\n throw new RangeError(\n `RedisErrorAdapter: side must be \"left\" or \"right\", received \"${String(options.side)}\".`\n );\n }\n if (this.key === '') {\n throw new RangeError('RedisErrorAdapter: key must not be empty.');\n }\n if (\n this.maxEntries !== undefined &&\n (!Number.isInteger(this.maxEntries) || this.maxEntries < 1)\n ) {\n throw new RangeError(\n `RedisErrorAdapter: maxEntries must be a positive integer, received ${String(this.maxEntries)}.`\n );\n }\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n const record = { id: crypto.randomUUID(), ...serializeEntry(entry) };\n if (this.mode === 'stream') {\n await this.client.sendCommand(this.buildStreamCommand(record));\n } else {\n await this.client.sendCommand([\n this.side === 'left' ? 'LPUSH' : 'RPUSH',\n this.key,\n JSON.stringify(record),\n ]);\n await this.trimList();\n }\n return { ok: true };\n } catch (error: unknown) {\n console.error(`${this.prefix} RedisErrorAdapter:`, error);\n return {\n ok: false,\n error: `RedisErrorAdapter failed to write to ${this.mode} \"${this.key}\": ${describeError(error)}`,\n };\n }\n }\n\n private buildStreamCommand(record: {\n id: string;\n code: string;\n message: string;\n cause?: unknown;\n source?: string;\n timestamp: string;\n }): string[] {\n const command: string[] = ['XADD', this.key];\n if (this.maxEntries !== undefined) {\n command.push('MAXLEN', '=', String(this.maxEntries));\n }\n command.push(\n '*',\n 'id',\n record.id,\n 'code',\n record.code,\n 'message',\n record.message,\n 'timestamp',\n record.timestamp\n );\n if (record.cause !== undefined) {\n command.push('cause', JSON.stringify(record.cause));\n }\n if (record.source !== undefined) {\n command.push('source', record.source);\n }\n return command;\n }\n\n /**\n * Drops the oldest list entries beyond `maxEntries`. The entry is already\n * stored by now, so a trim failure is reported but does not fail the call;\n * the next successful write trims the list again.\n */\n private async trimList(): Promise<void> {\n if (this.maxEntries === undefined) {\n return;\n }\n const keep: number = this.maxEntries;\n const [start, stop]: [string, string] =\n this.side === 'right' ? [String(-keep), '-1'] : ['0', String(keep - 1)];\n try {\n await this.client.sendCommand(['LTRIM', this.key, start, stop]);\n } catch (error: unknown) {\n console.error(\n `${this.prefix} RedisErrorAdapter: stored the entry but could not trim list \"${this.key}\" to ${keep} entries: ${describeError(error)}`\n );\n }\n }\n}\n","import { describeError, serializeEntry } from '../serialize.js';\nimport type {\n ErrorLogAdapter,\n ErrorLogEntry,\n ErrorLogResult,\n} from '../types.js';\n\n/** Default key prefix for stored objects. */\nexport const DEFAULT_S3_PREFIX = 'error_log/';\n\n/** Buffered entries kept after failed uploads: this many times `batchSize`, oldest dropped first. */\nexport const BUFFER_LIMIT_MULTIPLIER = 10;\n\n/** The parameters this adapter passes to `putObject`. */\nexport interface S3PutObjectInput {\n Bucket: string;\n Key: string;\n Body: string;\n ContentType: string;\n}\n\n/**\n * The only thing this adapter needs from an S3 client: `putObject`. The AWS\n * SDK v3 aggregated client (`new S3({ ... })` from `@aws-sdk/client-s3`) fits as\n * it is, and so does anything else that follows the S3 API, including\n * DigitalOcean Spaces, Cloudflare R2 and MinIO when you point the client at\n * their endpoint.\n */\nexport interface S3ClientLike {\n putObject(input: S3PutObjectInput): Promise<unknown>;\n}\n\nexport interface S3BatchOptions {\n /** Upload once this many entries are buffered. */\n batchSize: number;\n /**\n * Also upload when the oldest buffered entry has waited this long (ms). The\n * timer does not keep the process alive, so call `flush()` before exiting.\n */\n maxWaitMs?: number;\n}\n\nexport interface S3ErrorOptions {\n /**\n * Key prefix, for example `logs/app/`. A trailing slash is added if missing.\n * Defaults to `error_log/`. Use `''` for no prefix.\n */\n keyPrefix?: string;\n /**\n * Content type of the uploaded objects. Defaults to `application/json`, or\n * `application/x-ndjson` when batching.\n */\n contentType?: string;\n /**\n * Buffer entries and upload them together as one newline-delimited JSON\n * object. Off by default: one object per entry.\n */\n batch?: S3BatchOptions;\n /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */\n prefix?: string;\n}\n\ninterface S3Record {\n id: string;\n code: string;\n message: string;\n cause?: unknown;\n source?: string;\n timestamp: string;\n}\n\n/** Turns `2026-01-02T03:04:05.678Z` into `2026/01/02/20260102T030405678Z`. */\nfunction datePath(date: Date): string {\n const compact: string = date.toISOString().replace(/[-:.]/g, '');\n return `${compact.slice(0, 4)}/${compact.slice(4, 6)}/${compact.slice(6, 8)}/${compact}`;\n}\n\nfunction normalizePrefix(prefix: string): string {\n const trimmed: string = prefix.replace(/^\\/+/, '');\n return trimmed === '' || trimmed.endsWith('/') ? trimmed : `${trimmed}/`;\n}\n\n/**\n * Stores entries as objects in an S3-compatible bucket, keyed by date and UUID:\n * `error_log/2026/01/02/20260102T030405678Z-<uuid>.json`.\n *\n * By default each entry becomes its own JSON object and `log` resolves once the\n * upload succeeds. With `batch`, entries are buffered and uploaded together as\n * one `.ndjson` object; `log` then resolves ok as soon as the entry is\n * buffered (it is not stored yet), except for the call that fills the batch,\n * which resolves with the upload result. Call `flush()` before shutting down.\n */\nexport class S3ErrorAdapter<\n TCode extends string = string,\n> implements ErrorLogAdapter<TCode> {\n private readonly keyPrefix: string;\n private readonly contentType: string;\n private readonly batch: S3BatchOptions | undefined;\n private readonly logPrefix: string;\n private buffer: S3Record[] = [];\n private timer: ReturnType<typeof setTimeout> | null = null;\n private flushQueue: Promise<unknown> = Promise.resolve();\n\n /** @throws {RangeError} if `bucket` is empty or the batch options are invalid. */\n constructor(\n private readonly client: S3ClientLike,\n private readonly bucket: string,\n options: S3ErrorOptions = {}\n ) {\n this.keyPrefix = normalizePrefix(options.keyPrefix ?? DEFAULT_S3_PREFIX);\n this.batch = options.batch;\n this.contentType =\n options.contentType ??\n (this.batch === undefined ? 'application/json' : 'application/x-ndjson');\n this.logPrefix = options.prefix ?? '[error-log-adapter]';\n if (bucket === '') {\n throw new RangeError('S3ErrorAdapter: bucket must not be empty.');\n }\n if (this.batch !== undefined) {\n if (!Number.isInteger(this.batch.batchSize) || this.batch.batchSize < 1) {\n throw new RangeError(\n `S3ErrorAdapter: batch.batchSize must be a positive integer, received ${String(this.batch.batchSize)}.`\n );\n }\n if (\n this.batch.maxWaitMs !== undefined &&\n (!Number.isFinite(this.batch.maxWaitMs) || this.batch.maxWaitMs <= 0)\n ) {\n throw new RangeError(\n `S3ErrorAdapter: batch.maxWaitMs must be a positive number, received ${String(this.batch.maxWaitMs)}.`\n );\n }\n }\n }\n\n async log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult> {\n try {\n const record: S3Record = {\n id: crypto.randomUUID(),\n ...serializeEntry(entry),\n };\n const occurredAt: Date = new Date(record.timestamp);\n if (Number.isNaN(occurredAt.getTime())) {\n throw new RangeError(\n `timestamp \"${record.timestamp}\" is not a valid ISO 8601 date`\n );\n }\n if (this.batch === undefined) {\n const key: string = `${this.keyPrefix}${datePath(occurredAt)}-${record.id}.json`;\n await this.upload(key, `${JSON.stringify(record)}\\n`);\n return { ok: true };\n }\n return await this.addToBatch(record, this.batch);\n } catch (error: unknown) {\n return this.fail(error);\n }\n }\n\n /**\n * Uploads any buffered entries now. Resolves ok when nothing is buffered.\n * Entries from a failed upload stay buffered and are retried by the next flush.\n */\n flush(): Promise<ErrorLogResult> {\n const result: Promise<ErrorLogResult> = this.flushQueue.then(() =>\n this.uploadBuffered()\n );\n // uploadBuffered() never rejects, but keep the queue alive no matter what.\n this.flushQueue = result.catch(() => undefined);\n return result;\n }\n\n private async addToBatch(\n record: S3Record,\n batch: S3BatchOptions\n ): Promise<ErrorLogResult> {\n this.buffer.push(record);\n const limit: number = batch.batchSize * BUFFER_LIMIT_MULTIPLIER;\n if (this.buffer.length > limit) {\n const dropped: number = this.buffer.length - limit;\n this.buffer = this.buffer.slice(dropped);\n console.error(\n `${this.logPrefix} S3ErrorAdapter: dropped the ${dropped} oldest buffered entries because uploads to bucket \"${this.bucket}\" keep failing and the buffer is full (${limit} entries).`\n );\n }\n if (this.buffer.length >= batch.batchSize) {\n return this.flush();\n }\n this.scheduleFlush(batch);\n return { ok: true };\n }\n\n private scheduleFlush(batch: S3BatchOptions): void {\n if (batch.maxWaitMs === undefined || this.timer !== null) {\n return;\n }\n const timer = setTimeout((): void => {\n this.timer = null;\n void this.flush();\n }, batch.maxWaitMs);\n (timer as { unref?: () => void }).unref?.();\n this.timer = timer;\n }\n\n private async uploadBuffered(): Promise<ErrorLogResult> {\n if (this.timer !== null) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n const pending: S3Record[] = this.buffer.slice();\n if (pending.length === 0) {\n return { ok: true };\n }\n try {\n const key: string = `${this.keyPrefix}${datePath(new Date())}-${crypto.randomUUID()}.ndjson`;\n const body: string = pending\n .map((record: S3Record): string => JSON.stringify(record))\n .join('\\n');\n await this.upload(key, `${body}\\n`);\n const uploaded: Set<S3Record> = new Set(pending);\n this.buffer = this.buffer.filter(\n (record: S3Record): boolean => !uploaded.has(record)\n );\n return { ok: true };\n } catch (error: unknown) {\n if (this.batch !== undefined) {\n this.scheduleFlush(this.batch);\n }\n return this.fail(error, pending.length);\n }\n }\n\n private upload(key: string, body: string): Promise<unknown> {\n return this.client.putObject({\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: this.contentType,\n });\n }\n\n private fail(error: unknown, bufferedCount?: number): ErrorLogResult {\n console.error(`${this.logPrefix} S3ErrorAdapter:`, error);\n const retry: string =\n bufferedCount === undefined\n ? ''\n : ` (${bufferedCount} buffered ${bufferedCount === 1 ? 'entry was' : 'entries were'} kept for the next flush)`;\n return {\n ok: false,\n error: `S3ErrorAdapter failed to upload to bucket \"${this.bucket}\": ${describeError(error)}${retry}`,\n };\n }\n}\n"],"mappings":"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","names":["DEFAULT_TABLE_NAME","IDENTIFIER_PATTERN","assertValidTableName"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@richardmcquiston01/error-log-adapter",
3
- "version": "0.1.0",
4
- "description": "Dependency-free TypeScript error logging with pluggable adapters for console, webhook, and MongoDB sinks.",
3
+ "version": "0.3.0",
4
+ "description": "Dependency-free TypeScript error logging with pluggable adapters for console, webhook, MongoDB, Postgres, MySQL, SQLite, Redis, S3, IndexedDB, Web Storage, and file sinks.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Richard McQuiston <richard.mcquiston01@gmail.com>",
7
7
  "homepage": "https://github.com/RichardMcQuiston01/error-log-adapter#readme",
@@ -20,6 +20,13 @@
20
20
  "error-handling",
21
21
  "webhook",
22
22
  "mongodb",
23
+ "sqlite",
24
+ "postgres",
25
+ "mysql",
26
+ "redis",
27
+ "s3",
28
+ "indexeddb",
29
+ "ndjson",
23
30
  "zero-dependencies"
24
31
  ],
25
32
  "type": "module",