@richardmcquiston01/error-log-adapter 0.3.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.cjs +199 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +169 -1
- package/dist/index.d.ts +169 -1
- package/dist/index.mjs +195 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.d.cts
CHANGED
|
@@ -492,6 +492,11 @@ interface S3PutObjectInput {
|
|
|
492
492
|
* it is, and so does anything else that follows the S3 API, including
|
|
493
493
|
* DigitalOcean Spaces, Cloudflare R2 and MinIO when you point the client at
|
|
494
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'`.
|
|
495
500
|
*/
|
|
496
501
|
interface S3ClientLike {
|
|
497
502
|
putObject(input: S3PutObjectInput): Promise<unknown>;
|
|
@@ -559,4 +564,167 @@ declare class S3ErrorAdapter<TCode extends string = string> implements ErrorLogA
|
|
|
559
564
|
private fail;
|
|
560
565
|
}
|
|
561
566
|
|
|
562
|
-
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -492,6 +492,11 @@ interface S3PutObjectInput {
|
|
|
492
492
|
* it is, and so does anything else that follows the S3 API, including
|
|
493
493
|
* DigitalOcean Spaces, Cloudflare R2 and MinIO when you point the client at
|
|
494
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'`.
|
|
495
500
|
*/
|
|
496
501
|
interface S3ClientLike {
|
|
497
502
|
putObject(input: S3PutObjectInput): Promise<unknown>;
|
|
@@ -559,4 +564,167 @@ declare class S3ErrorAdapter<TCode extends string = string> implements ErrorLogA
|
|
|
559
564
|
private fail;
|
|
560
565
|
}
|
|
561
566
|
|
|
562
|
-
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -985,22 +985,217 @@ var S3ErrorAdapter = class {
|
|
|
985
985
|
};
|
|
986
986
|
}
|
|
987
987
|
};
|
|
988
|
+
|
|
989
|
+
// src/adapters/firebase-error-adapter.ts
|
|
990
|
+
var DEFAULT_WRITE_TIMEOUT_MS = 1e4;
|
|
991
|
+
function resolveTimeout(options, adapter) {
|
|
992
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_WRITE_TIMEOUT_MS;
|
|
993
|
+
if (Number.isNaN(timeoutMs) || timeoutMs <= 0) {
|
|
994
|
+
throw new RangeError(
|
|
995
|
+
`${adapter}: timeoutMs must be a positive number or Infinity, received ${String(options.timeoutMs)}.`
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
return timeoutMs;
|
|
999
|
+
}
|
|
1000
|
+
function waitForWrite(write, timeoutMs) {
|
|
1001
|
+
if (timeoutMs === Infinity) {
|
|
1002
|
+
return Promise.resolve(write);
|
|
1003
|
+
}
|
|
1004
|
+
return new Promise(
|
|
1005
|
+
(resolve, reject) => {
|
|
1006
|
+
const timer = setTimeout(() => {
|
|
1007
|
+
reject(
|
|
1008
|
+
new Error(
|
|
1009
|
+
`the write was not saved within ${timeoutMs} ms (the connection may be down; the SDK keeps the write queued and may still save it later)`
|
|
1010
|
+
)
|
|
1011
|
+
);
|
|
1012
|
+
}, timeoutMs);
|
|
1013
|
+
write.then(
|
|
1014
|
+
(value) => {
|
|
1015
|
+
clearTimeout(timer);
|
|
1016
|
+
resolve(value);
|
|
1017
|
+
},
|
|
1018
|
+
(reason) => {
|
|
1019
|
+
clearTimeout(timer);
|
|
1020
|
+
reject(reason);
|
|
1021
|
+
}
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
function buildRecord(entry) {
|
|
1027
|
+
const serialized = serializeEntry(entry);
|
|
1028
|
+
const record = {
|
|
1029
|
+
id: crypto.randomUUID(),
|
|
1030
|
+
code: serialized.code,
|
|
1031
|
+
message: serialized.message,
|
|
1032
|
+
timestamp: serialized.timestamp
|
|
1033
|
+
};
|
|
1034
|
+
if (serialized.source !== void 0) {
|
|
1035
|
+
record.source = serialized.source;
|
|
1036
|
+
}
|
|
1037
|
+
if (serialized.cause !== void 0) {
|
|
1038
|
+
record.cause = JSON.stringify(serialized.cause);
|
|
1039
|
+
}
|
|
1040
|
+
return record;
|
|
1041
|
+
}
|
|
1042
|
+
var FirestoreErrorAdapter = class {
|
|
1043
|
+
/** @throws {RangeError} if `options.timeoutMs` is not a positive number or `Infinity`. */
|
|
1044
|
+
constructor(collection, options = {}) {
|
|
1045
|
+
this.collection = collection;
|
|
1046
|
+
this.prefix = options.prefix ?? "[error-log-adapter]";
|
|
1047
|
+
this.timeoutMs = resolveTimeout(options, "FirestoreErrorAdapter");
|
|
1048
|
+
}
|
|
1049
|
+
async log(entry) {
|
|
1050
|
+
try {
|
|
1051
|
+
const record = buildRecord(entry);
|
|
1052
|
+
await waitForWrite(
|
|
1053
|
+
this.collection.doc(record.id).set(record),
|
|
1054
|
+
this.timeoutMs
|
|
1055
|
+
);
|
|
1056
|
+
return { ok: true };
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
console.error(`${this.prefix} FirestoreErrorAdapter:`, error);
|
|
1059
|
+
return {
|
|
1060
|
+
ok: false,
|
|
1061
|
+
error: `FirestoreErrorAdapter failed to write the document: ${describeError(error)}`
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
var RealtimeDatabaseErrorAdapter = class {
|
|
1067
|
+
/** @throws {RangeError} if `options.timeoutMs` is not a positive number or `Infinity`. */
|
|
1068
|
+
constructor(reference, options = {}) {
|
|
1069
|
+
this.reference = reference;
|
|
1070
|
+
this.prefix = options.prefix ?? "[error-log-adapter]";
|
|
1071
|
+
this.timeoutMs = resolveTimeout(options, "RealtimeDatabaseErrorAdapter");
|
|
1072
|
+
}
|
|
1073
|
+
async log(entry) {
|
|
1074
|
+
try {
|
|
1075
|
+
await waitForWrite(
|
|
1076
|
+
this.reference.push(buildRecord(entry)),
|
|
1077
|
+
this.timeoutMs
|
|
1078
|
+
);
|
|
1079
|
+
return { ok: true };
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
console.error(`${this.prefix} RealtimeDatabaseErrorAdapter:`, error);
|
|
1082
|
+
return {
|
|
1083
|
+
ok: false,
|
|
1084
|
+
error: `RealtimeDatabaseErrorAdapter failed to write the entry: ${describeError(error)}`
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
// src/adapters/supabase-error-adapter.ts
|
|
1091
|
+
var IDENTIFIER_PATTERN3 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1092
|
+
function buildSupabaseSetupSql(tableName = DEFAULT_TABLE_NAME2, options = {}) {
|
|
1093
|
+
const insertRoles = options.insertRoles ?? [];
|
|
1094
|
+
for (const role of insertRoles) {
|
|
1095
|
+
if (!IDENTIFIER_PATTERN3.test(role)) {
|
|
1096
|
+
throw new RangeError(
|
|
1097
|
+
`buildSupabaseSetupSql: invalid role name "${role}". Use only letters, digits and underscores, and do not start with a digit.`
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
const statements = [
|
|
1102
|
+
...buildErrorLogTableDdl("postgres", tableName),
|
|
1103
|
+
`ALTER TABLE "${tableName}" ENABLE ROW LEVEL SECURITY`
|
|
1104
|
+
];
|
|
1105
|
+
if (insertRoles.length > 0) {
|
|
1106
|
+
const policy = `${tableName}_insert_only`;
|
|
1107
|
+
statements.push(
|
|
1108
|
+
`DROP POLICY IF EXISTS "${policy}" ON "${tableName}"`,
|
|
1109
|
+
`CREATE POLICY "${policy}" ON "${tableName}" FOR INSERT TO ${insertRoles.map((role) => `"${role}"`).join(", ")} WITH CHECK (true)`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
return statements;
|
|
1113
|
+
}
|
|
1114
|
+
function describeSupabaseError(result) {
|
|
1115
|
+
const error = result.error ?? { message: "" };
|
|
1116
|
+
const parts = [];
|
|
1117
|
+
if (error.message) {
|
|
1118
|
+
parts.push(error.message);
|
|
1119
|
+
} else if (result.status) {
|
|
1120
|
+
parts.push(
|
|
1121
|
+
`HTTP ${result.status}${result.statusText ? ` ${result.statusText}` : ""} with no error message (a 404 usually means the table does not exist or is not exposed by the API)`
|
|
1122
|
+
);
|
|
1123
|
+
} else {
|
|
1124
|
+
parts.push("unknown error with no message");
|
|
1125
|
+
}
|
|
1126
|
+
if (error.code) {
|
|
1127
|
+
parts.push(`code ${error.code}`);
|
|
1128
|
+
}
|
|
1129
|
+
const detailsLine = (error.details ?? "").split("\n")[0].trim();
|
|
1130
|
+
if (detailsLine && detailsLine !== error.message) {
|
|
1131
|
+
parts.push(`details: ${detailsLine}`);
|
|
1132
|
+
}
|
|
1133
|
+
if (error.hint) {
|
|
1134
|
+
parts.push(`hint: ${error.hint}`);
|
|
1135
|
+
}
|
|
1136
|
+
return parts.join("; ");
|
|
1137
|
+
}
|
|
1138
|
+
var SupabaseErrorAdapter = class {
|
|
1139
|
+
/** @throws {RangeError} if `options.tableName` is empty. */
|
|
1140
|
+
constructor(client, options = {}) {
|
|
1141
|
+
this.client = client;
|
|
1142
|
+
this.tableName = options.tableName ?? DEFAULT_TABLE_NAME2;
|
|
1143
|
+
if (this.tableName.trim() === "") {
|
|
1144
|
+
throw new RangeError(
|
|
1145
|
+
"SupabaseErrorAdapter: tableName must not be empty."
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
this.prefix = options.prefix ?? "[error-log-adapter]";
|
|
1149
|
+
}
|
|
1150
|
+
async log(entry) {
|
|
1151
|
+
try {
|
|
1152
|
+
const serialized = serializeEntry(entry);
|
|
1153
|
+
const row = {
|
|
1154
|
+
id: crypto.randomUUID(),
|
|
1155
|
+
code: serialized.code,
|
|
1156
|
+
message: serialized.message,
|
|
1157
|
+
cause: serialized.cause === void 0 ? null : serialized.cause,
|
|
1158
|
+
source: serialized.source ?? null,
|
|
1159
|
+
occurred_at: serialized.timestamp
|
|
1160
|
+
};
|
|
1161
|
+
const result = await this.client.from(this.tableName).insert(row);
|
|
1162
|
+
if (result.error) {
|
|
1163
|
+
console.error(`${this.prefix} SupabaseErrorAdapter:`, result);
|
|
1164
|
+
return this.fail(describeSupabaseError(result));
|
|
1165
|
+
}
|
|
1166
|
+
return { ok: true };
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
console.error(`${this.prefix} SupabaseErrorAdapter:`, error);
|
|
1169
|
+
return this.fail(describeError(error));
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
fail(reason) {
|
|
1173
|
+
return {
|
|
1174
|
+
ok: false,
|
|
1175
|
+
error: `SupabaseErrorAdapter failed to insert into table "${this.tableName}": ${reason}`
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
988
1179
|
export {
|
|
989
1180
|
CompositeErrorAdapter,
|
|
990
1181
|
ConsoleErrorAdapter,
|
|
1182
|
+
FirestoreErrorAdapter,
|
|
991
1183
|
FlatFileErrorAdapter,
|
|
992
1184
|
IndexedDbErrorAdapter,
|
|
993
1185
|
JsonFileErrorAdapter,
|
|
994
1186
|
MongoDbErrorAdapter,
|
|
995
1187
|
MySqlErrorAdapter,
|
|
996
1188
|
PostgresErrorAdapter,
|
|
1189
|
+
RealtimeDatabaseErrorAdapter,
|
|
997
1190
|
RedisErrorAdapter,
|
|
998
1191
|
S3ErrorAdapter,
|
|
999
1192
|
SqliteErrorAdapter,
|
|
1193
|
+
SupabaseErrorAdapter,
|
|
1000
1194
|
WebStorageErrorAdapter,
|
|
1001
1195
|
WebhookErrorAdapter,
|
|
1002
1196
|
buildCreateErrorLogTableSql,
|
|
1003
1197
|
buildErrorLogTableDdl,
|
|
1198
|
+
buildSupabaseSetupSql,
|
|
1004
1199
|
createErrorLogStore,
|
|
1005
1200
|
formatErrorLogLine,
|
|
1006
1201
|
serializeCause,
|