@richardmcquiston01/error-log-adapter 0.2.0 → 0.4.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.d.ts CHANGED
@@ -355,4 +355,376 @@ declare class JsonFileErrorAdapter<TCode extends string = string> implements Err
355
355
  private readRecords;
356
356
  }
357
357
 
358
- export { type CollectionLike, CompositeErrorAdapter, ConsoleErrorAdapter, type ConsoleErrorOptions, type ErrorLogAdapter, type ErrorLogEntry, type ErrorLogResult, type FileSystemLike, FlatFileErrorAdapter, type FlatFileErrorOptions, type IdbDatabaseLike, type IdbObjectStoreLike, type IdbTransactionLike, type IdbUpgradeDatabaseLike, IndexedDbErrorAdapter, type IndexedDbErrorOptions, type IndexedDbErrorRecord, JsonFileErrorAdapter, type JsonFileErrorOptions, type JsonFileErrorRecord, MongoDbErrorAdapter, type MongoDbErrorOptions, type ReadWriteFileSystemLike, type SerializedErrorLogEntry, type SqliteClientLike, SqliteErrorAdapter, type SqliteErrorOptions, type StorageLike, WebStorageErrorAdapter, type WebStorageErrorOptions, type WebStorageErrorRecord, WebhookErrorAdapter, type WebhookErrorOptions, buildCreateErrorLogTableSql, createErrorLogStore, formatErrorLogLine, serializeCause, serializeEntry };
358
+ type SqlDialect = 'postgres' | 'mysql';
359
+ /**
360
+ * The only thing these adapters need from a database driver: run one
361
+ * parameterized statement. `pg` (`Pool` / `Client`) and `mysql2/promise`
362
+ * (`Pool` / `Connection`) both fit as they are.
363
+ */
364
+ interface SqlClientLike {
365
+ query(sql: string, params: unknown[]): Promise<unknown>;
366
+ }
367
+ interface SqlErrorOptions {
368
+ /** Table to write to. Defaults to `error_log`. Letters, digits and `_` only, up to 47 characters. */
369
+ tableName?: string;
370
+ /** Create the table (and index) on first use. Defaults to `true`. */
371
+ createTable?: boolean;
372
+ /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
373
+ prefix?: string;
374
+ }
375
+ /**
376
+ * Returns the statements that create the error log table and its index for a
377
+ * dialect. Use this to run your own migration when `createTable` is disabled.
378
+ *
379
+ * Postgres gets a native `UUID` key, `JSONB` for the cause, and `TIMESTAMPTZ`
380
+ * columns. MySQL gets a `CHAR(36)` UUID key (it has no UUID type), a `JSON`
381
+ * cause, and `DATETIME(3)` columns holding UTC times.
382
+ */
383
+ declare function buildErrorLogTableDdl(dialect: SqlDialect, tableName?: string): string[];
384
+ /** Shared by the Postgres and MySQL adapters: only the SQL text and time format differ. */
385
+ declare abstract class SqlErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
386
+ private readonly client;
387
+ private readonly dialect;
388
+ private readonly adapterName;
389
+ private readonly tableName;
390
+ private readonly createTable;
391
+ private readonly prefix;
392
+ private tableReady;
393
+ protected constructor(client: SqlClientLike, dialect: SqlDialect, adapterName: string, options: SqlErrorOptions);
394
+ log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
395
+ private insertSql;
396
+ /** Creates the table once. A failed attempt is retried on the next log call. */
397
+ private ensureTable;
398
+ private runCreateStatements;
399
+ }
400
+ /**
401
+ * Writes entries to Postgres. Pass a `pg` `Pool` or `Client`.
402
+ *
403
+ * ```ts
404
+ * const adapter = new PostgresErrorAdapter(new Pool({ connectionString }));
405
+ * ```
406
+ */
407
+ declare class PostgresErrorAdapter<TCode extends string = string> extends SqlErrorAdapter<TCode> {
408
+ /** @throws {RangeError} if `options.tableName` is not a plain identifier of 47 characters or fewer. */
409
+ constructor(client: SqlClientLike, options?: SqlErrorOptions);
410
+ }
411
+ /**
412
+ * Writes entries to MySQL or MariaDB. Pass a `mysql2/promise` `Pool` or `Connection`.
413
+ *
414
+ * ```ts
415
+ * const adapter = new MySqlErrorAdapter(mysql.createPool(connectionUri));
416
+ * ```
417
+ */
418
+ declare class MySqlErrorAdapter<TCode extends string = string> extends SqlErrorAdapter<TCode> {
419
+ /** @throws {RangeError} if `options.tableName` is not a plain identifier of 47 characters or fewer. */
420
+ constructor(client: SqlClientLike, options?: SqlErrorOptions);
421
+ }
422
+
423
+ /**
424
+ * The only thing this adapter needs from a Redis client: send one raw command
425
+ * as an array of strings. node-redis (`client.sendCommand`) already matches.
426
+ * ioredis needs a one-line wrapper:
427
+ *
428
+ * ```ts
429
+ * const client: RedisClientLike = {
430
+ * sendCommand: (args) => redis.call(args[0] as string, ...args.slice(1)),
431
+ * };
432
+ * ```
433
+ */
434
+ interface RedisClientLike {
435
+ sendCommand(args: string[]): Promise<unknown>;
436
+ }
437
+ interface RedisErrorOptions {
438
+ /**
439
+ * `list` (default) pushes each entry onto a Redis list as one JSON string.
440
+ * `stream` appends each entry to a Redis stream (`XADD`) as separate fields.
441
+ */
442
+ mode?: 'list' | 'stream';
443
+ /** Key to write to. Defaults to `error_log`. */
444
+ key?: string;
445
+ /**
446
+ * Most entries to keep. The oldest are dropped first. Unlimited by default.
447
+ * Lists are trimmed with `LTRIM` after the push; streams use an exact `MAXLEN`.
448
+ */
449
+ maxEntries?: number;
450
+ /**
451
+ * List mode only: which end to push onto. `right` (default, `RPUSH`) keeps
452
+ * the newest entry last; `left` (`LPUSH`) keeps the newest entry first.
453
+ */
454
+ side?: 'left' | 'right';
455
+ /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
456
+ prefix?: string;
457
+ }
458
+ /**
459
+ * Writes entries to Redis, either as a capped list of JSON strings or as a
460
+ * stream. Each record holds a UUID `id` plus the serialized entry. In stream
461
+ * mode `cause` and `source` fields are left out when the entry has none.
462
+ */
463
+ declare class RedisErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
464
+ private readonly client;
465
+ private readonly mode;
466
+ private readonly key;
467
+ private readonly maxEntries;
468
+ private readonly side;
469
+ private readonly prefix;
470
+ /** @throws {RangeError} if `key` is empty, `maxEntries` is not a positive integer, or `mode` / `side` is unknown. */
471
+ constructor(client: RedisClientLike, options?: RedisErrorOptions);
472
+ log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
473
+ private buildStreamCommand;
474
+ /**
475
+ * Drops the oldest list entries beyond `maxEntries`. The entry is already
476
+ * stored by now, so a trim failure is reported but does not fail the call;
477
+ * the next successful write trims the list again.
478
+ */
479
+ private trimList;
480
+ }
481
+
482
+ /** The parameters this adapter passes to `putObject`. */
483
+ interface S3PutObjectInput {
484
+ Bucket: string;
485
+ Key: string;
486
+ Body: string;
487
+ ContentType: string;
488
+ }
489
+ /**
490
+ * The only thing this adapter needs from an S3 client: `putObject`. The AWS
491
+ * SDK v3 aggregated client (`new S3({ ... })` from `@aws-sdk/client-s3`) fits as
492
+ * it is, and so does anything else that follows the S3 API, including
493
+ * DigitalOcean Spaces, Cloudflare R2 and MinIO when you point the client at
494
+ * their endpoint.
495
+ *
496
+ * Some S3-compatible providers reject the checksum headers that recent AWS SDK
497
+ * v3 versions add by default. If uploads fail with a checksum or header error,
498
+ * create the client with `requestChecksumCalculation: 'WHEN_REQUIRED'` and
499
+ * `responseChecksumValidation: 'WHEN_REQUIRED'`.
500
+ */
501
+ interface S3ClientLike {
502
+ putObject(input: S3PutObjectInput): Promise<unknown>;
503
+ }
504
+ interface S3BatchOptions {
505
+ /** Upload once this many entries are buffered. */
506
+ batchSize: number;
507
+ /**
508
+ * Also upload when the oldest buffered entry has waited this long (ms). The
509
+ * timer does not keep the process alive, so call `flush()` before exiting.
510
+ */
511
+ maxWaitMs?: number;
512
+ }
513
+ interface S3ErrorOptions {
514
+ /**
515
+ * Key prefix, for example `logs/app/`. A trailing slash is added if missing.
516
+ * Defaults to `error_log/`. Use `''` for no prefix.
517
+ */
518
+ keyPrefix?: string;
519
+ /**
520
+ * Content type of the uploaded objects. Defaults to `application/json`, or
521
+ * `application/x-ndjson` when batching.
522
+ */
523
+ contentType?: string;
524
+ /**
525
+ * Buffer entries and upload them together as one newline-delimited JSON
526
+ * object. Off by default: one object per entry.
527
+ */
528
+ batch?: S3BatchOptions;
529
+ /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
530
+ prefix?: string;
531
+ }
532
+ /**
533
+ * Stores entries as objects in an S3-compatible bucket, keyed by date and UUID:
534
+ * `error_log/2026/01/02/20260102T030405678Z-<uuid>.json`.
535
+ *
536
+ * By default each entry becomes its own JSON object and `log` resolves once the
537
+ * upload succeeds. With `batch`, entries are buffered and uploaded together as
538
+ * one `.ndjson` object; `log` then resolves ok as soon as the entry is
539
+ * buffered (it is not stored yet), except for the call that fills the batch,
540
+ * which resolves with the upload result. Call `flush()` before shutting down.
541
+ */
542
+ declare class S3ErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
543
+ private readonly client;
544
+ private readonly bucket;
545
+ private readonly keyPrefix;
546
+ private readonly contentType;
547
+ private readonly batch;
548
+ private readonly logPrefix;
549
+ private buffer;
550
+ private timer;
551
+ private flushQueue;
552
+ /** @throws {RangeError} if `bucket` is empty or the batch options are invalid. */
553
+ constructor(client: S3ClientLike, bucket: string, options?: S3ErrorOptions);
554
+ log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
555
+ /**
556
+ * Uploads any buffered entries now. Resolves ok when nothing is buffered.
557
+ * Entries from a failed upload stay buffered and are retried by the next flush.
558
+ */
559
+ flush(): Promise<ErrorLogResult>;
560
+ private addToBatch;
561
+ private scheduleFlush;
562
+ private uploadBuffered;
563
+ private upload;
564
+ private fail;
565
+ }
566
+
567
+ /** The fields stored for each entry, in Firestore and in the Realtime Database. */
568
+ interface FirebaseErrorRecord {
569
+ /** UUID. In Firestore it is also the document id. */
570
+ id: string;
571
+ code: string;
572
+ message: string;
573
+ /** ISO 8601 timestamp of the entry. */
574
+ timestamp: string;
575
+ source?: string;
576
+ /**
577
+ * The entry's cause as a JSON string. Plain text avoids Firestore's limits on
578
+ * custom objects and nested arrays, and the Realtime Database's limits on key
579
+ * characters, so an odd cause can never make the write fail.
580
+ */
581
+ cause?: string;
582
+ }
583
+ /**
584
+ * The only thing the Firestore adapter needs from a collection: `doc(id).set(data)`.
585
+ * A `firebase-admin` collection (`getFirestore().collection('error_log')`) fits
586
+ * as it is. With the web SDK, wrap it:
587
+ *
588
+ * ```ts
589
+ * const collectionLike: FirestoreCollectionLike = {
590
+ * doc: (id) => ({ set: (data) => setDoc(doc(db, 'error_log', id), data) }),
591
+ * };
592
+ * ```
593
+ */
594
+ interface FirestoreCollectionLike {
595
+ doc(documentId: string): {
596
+ set(data: FirebaseErrorRecord): Promise<unknown>;
597
+ };
598
+ }
599
+ /**
600
+ * The only thing the Realtime Database adapter needs from a reference:
601
+ * `push(value)`, which resolves once the write is saved. A `firebase-admin`
602
+ * reference (`getDatabase().ref('error_log')`) fits as it is. With the web SDK, wrap it:
603
+ *
604
+ * ```ts
605
+ * const refLike: RealtimeDatabaseRefLike = {
606
+ * push: (value) => push(ref(db, 'error_log'), value),
607
+ * };
608
+ * ```
609
+ */
610
+ interface RealtimeDatabaseRefLike {
611
+ push(value: FirebaseErrorRecord): PromiseLike<unknown>;
612
+ }
613
+ interface FirebaseErrorOptions {
614
+ /**
615
+ * How long `log` waits for the write to be saved before giving up, in
616
+ * milliseconds. Defaults to 10000. Pass `Infinity` to wait without limit.
617
+ *
618
+ * The Firebase SDKs keep writes queued while the connection is down, and
619
+ * their promises stay pending until it returns, so without a limit `log`
620
+ * could hang for as long as an outage lasts. A write that timed out is still
621
+ * queued by the SDK and may be saved later.
622
+ */
623
+ timeoutMs?: number;
624
+ /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
625
+ prefix?: string;
626
+ }
627
+ /**
628
+ * Writes each entry as a document in a Firestore collection. The document id is
629
+ * the entry's UUID, so retrying a write never creates a duplicate.
630
+ */
631
+ declare class FirestoreErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
632
+ private readonly collection;
633
+ private readonly prefix;
634
+ private readonly timeoutMs;
635
+ /** @throws {RangeError} if `options.timeoutMs` is not a positive number or `Infinity`. */
636
+ constructor(collection: FirestoreCollectionLike, options?: FirebaseErrorOptions);
637
+ log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
638
+ }
639
+ /**
640
+ * Writes each entry under a Realtime Database reference with `push`, which
641
+ * generates a chronologically ordered key.
642
+ */
643
+ declare class RealtimeDatabaseErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
644
+ private readonly reference;
645
+ private readonly prefix;
646
+ private readonly timeoutMs;
647
+ /** @throws {RangeError} if `options.timeoutMs` is not a positive number or `Infinity`. */
648
+ constructor(reference: RealtimeDatabaseRefLike, options?: FirebaseErrorOptions);
649
+ log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
650
+ }
651
+
652
+ /** The row inserted for each entry. The columns match the Postgres adapter's table. */
653
+ interface SupabaseErrorRow {
654
+ /** UUID primary key. */
655
+ id: string;
656
+ code: string;
657
+ message: string;
658
+ /** The cause as JSON, or `null` when the entry has none. */
659
+ cause: unknown;
660
+ source: string | null;
661
+ /** ISO 8601 timestamp of the entry. */
662
+ occurred_at: string;
663
+ }
664
+ /** The error object `supabase-js` returns (a `PostgrestError`) instead of throwing. */
665
+ interface SupabaseErrorLike {
666
+ message: string;
667
+ code?: string;
668
+ details?: string | null;
669
+ hint?: string | null;
670
+ }
671
+ /** What `insert` resolves to. The status fields are used when the error has no message. */
672
+ interface SupabaseInsertResult {
673
+ error: SupabaseErrorLike | null;
674
+ status?: number;
675
+ statusText?: string;
676
+ }
677
+ /**
678
+ * The only thing the Supabase adapter needs from a client: `from(table).insert(row)`.
679
+ * A `supabase-js` client (`createClient(url, key)`) fits as it is.
680
+ */
681
+ interface SupabaseClientLike {
682
+ from(table: string): {
683
+ insert(row: SupabaseErrorRow): PromiseLike<SupabaseInsertResult>;
684
+ };
685
+ }
686
+ interface SupabaseErrorOptions {
687
+ /** Table to write to. Defaults to `error_log`. */
688
+ tableName?: string;
689
+ /** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
690
+ prefix?: string;
691
+ }
692
+ interface SupabaseSetupOptions {
693
+ /**
694
+ * Postgres roles allowed to insert, for example `['anon', 'authenticated']`
695
+ * when the browser logs with the public key. Leave empty when only a server
696
+ * using the service role key logs: that role bypasses row level security.
697
+ */
698
+ insertRoles?: string[];
699
+ }
700
+ /**
701
+ * Returns the SQL to run once in the Supabase SQL editor: the error log table,
702
+ * with row level security turned on (as Supabase recommends for every table in
703
+ * an exposed schema) and, optionally, an insert-only policy for the given roles.
704
+ * Nobody but the service role can read the log through the API.
705
+ *
706
+ * @throws {RangeError} if the table name or a role is not a plain identifier.
707
+ */
708
+ declare function buildSupabaseSetupSql(tableName?: string, options?: SupabaseSetupOptions): string[];
709
+ /**
710
+ * Writes entries to a Supabase table through `supabase-js`. Create the table
711
+ * first with `buildSupabaseSetupSql` (or `buildErrorLogTableDdl('postgres')`).
712
+ *
713
+ * ```ts
714
+ * const adapter = new SupabaseErrorAdapter(createClient(url, serviceRoleKey));
715
+ * ```
716
+ *
717
+ * `supabase-js` reports failures in a returned `error` field rather than
718
+ * throwing; the adapter turns both that and a thrown error into `{ ok: false }`.
719
+ */
720
+ declare class SupabaseErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
721
+ private readonly client;
722
+ private readonly tableName;
723
+ private readonly prefix;
724
+ /** @throws {RangeError} if `options.tableName` is empty. */
725
+ constructor(client: SupabaseClientLike, options?: SupabaseErrorOptions);
726
+ log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
727
+ private fail;
728
+ }
729
+
730
+ export { type CollectionLike, CompositeErrorAdapter, ConsoleErrorAdapter, type ConsoleErrorOptions, type ErrorLogAdapter, type ErrorLogEntry, type ErrorLogResult, type FileSystemLike, type FirebaseErrorOptions, type FirebaseErrorRecord, type FirestoreCollectionLike, FirestoreErrorAdapter, FlatFileErrorAdapter, type FlatFileErrorOptions, type IdbDatabaseLike, type IdbObjectStoreLike, type IdbTransactionLike, type IdbUpgradeDatabaseLike, IndexedDbErrorAdapter, type IndexedDbErrorOptions, type IndexedDbErrorRecord, JsonFileErrorAdapter, type JsonFileErrorOptions, type JsonFileErrorRecord, MongoDbErrorAdapter, type MongoDbErrorOptions, MySqlErrorAdapter, PostgresErrorAdapter, type ReadWriteFileSystemLike, RealtimeDatabaseErrorAdapter, type RealtimeDatabaseRefLike, type RedisClientLike, RedisErrorAdapter, type RedisErrorOptions, type S3BatchOptions, type S3ClientLike, S3ErrorAdapter, type S3ErrorOptions, type S3PutObjectInput, type SerializedErrorLogEntry, type SqlClientLike, type SqlDialect, type SqlErrorOptions, type SqliteClientLike, SqliteErrorAdapter, type SqliteErrorOptions, type StorageLike, type SupabaseClientLike, SupabaseErrorAdapter, type SupabaseErrorLike, type SupabaseErrorOptions, type SupabaseErrorRow, type SupabaseInsertResult, type SupabaseSetupOptions, WebStorageErrorAdapter, type WebStorageErrorOptions, type WebStorageErrorRecord, WebhookErrorAdapter, type WebhookErrorOptions, buildCreateErrorLogTableSql, buildErrorLogTableDdl, buildSupabaseSetupSql, createErrorLogStore, formatErrorLogLine, serializeCause, serializeEntry };