@richardmcquiston01/error-log-adapter 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -13
- package/dist/index.cjs +771 -156
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +383 -13
- package/dist/index.d.ts +383 -13
- package/dist/index.mjs +766 -156
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,17 @@ interface ErrorLogEntry<TCode extends string = string> {
|
|
|
10
10
|
source?: string;
|
|
11
11
|
/** ISO 8601 timestamp. Adapters fill this in when it is omitted. */
|
|
12
12
|
timestamp?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Optional UUID that identifies this entry. Adapters generate a new one for
|
|
15
|
+
* every `log` call when it is omitted, so logging the same entry twice stores
|
|
16
|
+
* it twice. Supply the same `id` (and, for S3, the same `timestamp`) to make
|
|
17
|
+
* a retry safe: sinks that can detect a repeat overwrite or skip the existing
|
|
18
|
+
* record instead of adding another. It must be a UUID, for example from
|
|
19
|
+
* `crypto.randomUUID()`; anything else makes `log` return `ok: false`.
|
|
20
|
+
* Redis, the flat file and NDJSON file adapters, and the console cannot
|
|
21
|
+
* detect a repeat.
|
|
22
|
+
*/
|
|
23
|
+
id?: string;
|
|
13
24
|
}
|
|
14
25
|
/** Outcome of a `log` call. Callers should check `ok`. */
|
|
15
26
|
interface ErrorLogResult {
|
|
@@ -34,8 +45,16 @@ interface SerializedErrorLogEntry {
|
|
|
34
45
|
timestamp: string;
|
|
35
46
|
}
|
|
36
47
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
48
|
+
* Returns a copy of `cause` that `JSON.stringify` can always handle, so a
|
|
49
|
+
* strange cause can never stop an entry from being stored.
|
|
50
|
+
*
|
|
51
|
+
* - `Error` objects (including ones from another realm) become
|
|
52
|
+
* `{ name, message, stack }`, plus `cause` and, for an `AggregateError`,
|
|
53
|
+
* `errors`, which are converted the same way.
|
|
54
|
+
* - Circular references become `"[Circular]"`, and anything nested deeper than
|
|
55
|
+
* `MAX_CAUSE_DEPTH` levels becomes `"[Truncated]"`.
|
|
56
|
+
* - `BigInt`, `Symbol` and function values, and non-finite numbers, become strings.
|
|
57
|
+
* - A property or `toJSON` that throws is replaced by a short description.
|
|
39
58
|
*/
|
|
40
59
|
declare function serializeCause(cause: unknown): unknown;
|
|
41
60
|
/** Returns a JSON-safe copy of the entry, adding a timestamp if missing. */
|
|
@@ -56,13 +75,34 @@ interface WebhookErrorOptions {
|
|
|
56
75
|
headers?: Record<string, string>;
|
|
57
76
|
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
58
77
|
prefix?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Allow a plain `http://` URL. Entries contain error messages and stack
|
|
80
|
+
* traces, so they are only sent over `https://` unless this is `true`.
|
|
81
|
+
* `http://localhost`, `127.0.0.1` and `[::1]` are always allowed.
|
|
82
|
+
* Defaults to `false`.
|
|
83
|
+
*/
|
|
84
|
+
allowInsecure?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* How long to wait for the webhook to answer, in milliseconds. Defaults to
|
|
87
|
+
* 10000. Pass `Infinity` to wait without limit. When it passes, the request is
|
|
88
|
+
* cancelled, so the connection is closed rather than left open; the server may
|
|
89
|
+
* still have received the entry.
|
|
90
|
+
*/
|
|
91
|
+
timeoutMs?: number;
|
|
59
92
|
}
|
|
60
93
|
declare class WebhookErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
61
94
|
private readonly url;
|
|
62
95
|
private readonly options;
|
|
63
96
|
private readonly prefix;
|
|
97
|
+
private readonly timeoutMs;
|
|
98
|
+
/** Scheme and host only. Webhook URLs often carry a secret in the path or query. */
|
|
99
|
+
private readonly target;
|
|
100
|
+
/** @throws {RangeError} if `url` is not a valid `https://` URL (see `allowInsecure`) or `timeoutMs` is invalid. */
|
|
64
101
|
constructor(url: string, options?: WebhookErrorOptions);
|
|
65
102
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
103
|
+
private redirectMessage;
|
|
104
|
+
/** Replaces the full URL in a message with its origin, in case the runtime put it there. */
|
|
105
|
+
private redact;
|
|
66
106
|
private fail;
|
|
67
107
|
}
|
|
68
108
|
|
|
@@ -73,26 +113,63 @@ interface CollectionLike {
|
|
|
73
113
|
interface MongoDbErrorOptions {
|
|
74
114
|
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
75
115
|
prefix?: string;
|
|
116
|
+
/**
|
|
117
|
+
* How long `log` waits for the insert before giving up, in milliseconds. Defaults to
|
|
118
|
+
* 10000. Pass `Infinity` to wait without limit. The call cannot be cancelled,
|
|
119
|
+
* so it may still finish after `log` has returned a failure.
|
|
120
|
+
*/
|
|
121
|
+
timeoutMs?: number;
|
|
76
122
|
}
|
|
77
123
|
declare class MongoDbErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
78
124
|
private readonly collection;
|
|
79
125
|
private readonly prefix;
|
|
126
|
+
private readonly timeoutMs;
|
|
127
|
+
/** @throws {RangeError} if `options.timeoutMs` is not a positive number or Infinity. */
|
|
80
128
|
constructor(collection: CollectionLike, options?: MongoDbErrorOptions);
|
|
81
129
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
82
130
|
}
|
|
83
131
|
|
|
132
|
+
interface CompositeErrorOptions {
|
|
133
|
+
/**
|
|
134
|
+
* How long to wait for each adapter before reporting it as failed, in
|
|
135
|
+
* milliseconds. Defaults to 30000, longer than the 10000 default of the built-in
|
|
136
|
+
* adapters so their own, more specific timeout messages come first. This only
|
|
137
|
+
* matters for a custom adapter that can hang: one stuck adapter then cannot
|
|
138
|
+
* hold back the result of the others. Pass `Infinity` to wait without limit.
|
|
139
|
+
*/
|
|
140
|
+
timeoutMs?: number;
|
|
141
|
+
}
|
|
84
142
|
/** Sends every entry to several adapters. Succeeds only if all of them do. */
|
|
85
143
|
declare class CompositeErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
86
144
|
private readonly adapters;
|
|
87
|
-
|
|
145
|
+
private readonly timeoutMs;
|
|
146
|
+
/** @throws {RangeError} if `options.timeoutMs` is not a positive number or Infinity. */
|
|
147
|
+
constructor(adapters: ReadonlyArray<ErrorLogAdapter<TCode>>, options?: CompositeErrorOptions);
|
|
88
148
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
89
149
|
}
|
|
90
150
|
|
|
91
151
|
/** Event handler slot. Typed loosely so real DOM `IDBTransaction` objects fit. */
|
|
92
152
|
type IdbHandler = ((event: never) => unknown) | null;
|
|
153
|
+
/** The error object of a failed request or transaction (a `DOMException`). */
|
|
154
|
+
interface IdbErrorLike {
|
|
155
|
+
name?: string;
|
|
156
|
+
message?: string;
|
|
157
|
+
}
|
|
158
|
+
/** The subset of an `IDBRequest` this adapter uses. */
|
|
159
|
+
interface IdbRequestLike {
|
|
160
|
+
onerror: IdbHandler;
|
|
161
|
+
readonly error?: IdbErrorLike | null;
|
|
162
|
+
}
|
|
93
163
|
/** The subset of an `IDBObjectStore` this adapter uses. */
|
|
94
164
|
interface IdbObjectStoreLike {
|
|
165
|
+
/** Returns an `IDBRequest`. Fakes that return nothing also work. */
|
|
95
166
|
add(value: unknown): unknown;
|
|
167
|
+
/**
|
|
168
|
+
* Replaces the record with the same key. Used for entries that carry their own
|
|
169
|
+
* `id`, so logging one again does not fail on the existing key. A real
|
|
170
|
+
* `IDBObjectStore` has it; a fake without it falls back to `add`.
|
|
171
|
+
*/
|
|
172
|
+
put?(value: unknown): unknown;
|
|
96
173
|
}
|
|
97
174
|
/** The subset of an `IDBTransaction` this adapter uses. */
|
|
98
175
|
interface IdbTransactionLike {
|
|
@@ -100,9 +177,7 @@ interface IdbTransactionLike {
|
|
|
100
177
|
oncomplete: IdbHandler;
|
|
101
178
|
onerror: IdbHandler;
|
|
102
179
|
onabort: IdbHandler;
|
|
103
|
-
readonly error?:
|
|
104
|
-
message?: string;
|
|
105
|
-
} | null;
|
|
180
|
+
readonly error?: IdbErrorLike | null;
|
|
106
181
|
}
|
|
107
182
|
/** The subset of an `IDBDatabase` this adapter uses. */
|
|
108
183
|
interface IdbDatabaseLike {
|
|
@@ -128,6 +203,13 @@ interface IndexedDbErrorOptions {
|
|
|
128
203
|
storeName?: string;
|
|
129
204
|
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
130
205
|
prefix?: string;
|
|
206
|
+
/**
|
|
207
|
+
* How long `log` waits for the transaction to finish, in milliseconds. Defaults
|
|
208
|
+
* to 10000. Pass `Infinity` to wait without limit. A transaction can stay
|
|
209
|
+
* pending when a version change in another tab blocks it, or when the browser
|
|
210
|
+
* has evicted the origin's storage. The write may still finish afterwards.
|
|
211
|
+
*/
|
|
212
|
+
timeoutMs?: number;
|
|
131
213
|
}
|
|
132
214
|
/**
|
|
133
215
|
* Creates the error log object store (UUID `id` key, `timestamp` index).
|
|
@@ -138,9 +220,18 @@ declare class IndexedDbErrorAdapter<TCode extends string = string> implements Er
|
|
|
138
220
|
private readonly database;
|
|
139
221
|
private readonly storeName;
|
|
140
222
|
private readonly prefix;
|
|
223
|
+
private readonly timeoutMs;
|
|
224
|
+
/** @throws {RangeError} if `options.timeoutMs` is not a positive number or Infinity. */
|
|
141
225
|
constructor(database: IdbDatabaseLike, options?: IndexedDbErrorOptions);
|
|
142
226
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
143
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* Resolves once the transaction commits; rejects if the request fails, the
|
|
229
|
+
* transaction aborts, or it does not finish within `timeoutMs`.
|
|
230
|
+
*
|
|
231
|
+
* Error events are handled here (`preventDefault` and `stopPropagation`), so a
|
|
232
|
+
* failed log write does not reach the `onerror` handler an app may have set on
|
|
233
|
+
* its own database object.
|
|
234
|
+
*/
|
|
144
235
|
private write;
|
|
145
236
|
}
|
|
146
237
|
|
|
@@ -166,6 +257,12 @@ interface SqliteErrorOptions {
|
|
|
166
257
|
createTable?: boolean;
|
|
167
258
|
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
168
259
|
prefix?: string;
|
|
260
|
+
/**
|
|
261
|
+
* How long `log` waits for the driver before giving up, in milliseconds. Defaults
|
|
262
|
+
* to 10000. Pass `Infinity` to wait without limit. The call cannot be
|
|
263
|
+
* cancelled, so it may still finish after `log` has returned a failure.
|
|
264
|
+
*/
|
|
265
|
+
timeoutMs?: number;
|
|
169
266
|
}
|
|
170
267
|
/**
|
|
171
268
|
* Returns the statements that create the error log table and its index.
|
|
@@ -177,13 +274,16 @@ declare class SqliteErrorAdapter<TCode extends string = string> implements Error
|
|
|
177
274
|
private readonly tableName;
|
|
178
275
|
private readonly createTable;
|
|
179
276
|
private readonly prefix;
|
|
277
|
+
private readonly timeoutMs;
|
|
180
278
|
private tableReady;
|
|
181
|
-
/** @throws {RangeError} if `options.tableName` is not a plain identifier. */
|
|
279
|
+
/** @throws {RangeError} if `options.tableName` is not a plain identifier or `options.timeoutMs` is invalid. */
|
|
182
280
|
constructor(client: SqliteClientLike, options?: SqliteErrorOptions);
|
|
183
281
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
184
282
|
/** Creates the table once. A failed attempt is retried on the next log call. */
|
|
185
283
|
private ensureTable;
|
|
186
284
|
private runCreateStatements;
|
|
285
|
+
/** Runs one statement, giving up waiting after `timeoutMs`. */
|
|
286
|
+
private run;
|
|
187
287
|
}
|
|
188
288
|
|
|
189
289
|
/**
|
|
@@ -194,6 +294,14 @@ interface StorageLike {
|
|
|
194
294
|
getItem(key: string): string | null;
|
|
195
295
|
setItem(key: string, value: string): void;
|
|
196
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* The part of `navigator.locks` this adapter uses. `localStorage` is shared by
|
|
299
|
+
* every tab of an origin and has no atomic update, so a lock keeps two tabs from
|
|
300
|
+
* overwriting each other's entry.
|
|
301
|
+
*/
|
|
302
|
+
interface LockManagerLike {
|
|
303
|
+
request<T>(name: string, callback: () => T | Promise<T>): Promise<T>;
|
|
304
|
+
}
|
|
197
305
|
/** A stored record: the serialized entry plus its generated UUID. */
|
|
198
306
|
interface WebStorageErrorRecord extends SerializedErrorLogEntry {
|
|
199
307
|
id: string;
|
|
@@ -205,24 +313,51 @@ interface WebStorageErrorOptions {
|
|
|
205
313
|
maxEntries?: number;
|
|
206
314
|
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
207
315
|
prefix?: string;
|
|
316
|
+
/**
|
|
317
|
+
* Lock manager that makes tabs sharing a `localStorage` take turns. Defaults to
|
|
318
|
+
* `navigator.locks` when the browser has it. This reduces lost entries but does
|
|
319
|
+
* not remove them: a tab in another process can still read a stale copy
|
|
320
|
+
* (measured in Chromium). Pass `null` to turn locking off.
|
|
321
|
+
*/
|
|
322
|
+
locks?: LockManagerLike | null;
|
|
208
323
|
}
|
|
209
324
|
/**
|
|
210
325
|
* Keeps the newest entries in a Web Storage object (`sessionStorage` or
|
|
211
326
|
* `localStorage`) as a JSON array under one key, capped at `maxEntries`.
|
|
212
327
|
* When the store is full, the oldest entries are dropped until the new one fits.
|
|
328
|
+
*
|
|
329
|
+
* `sessionStorage` belongs to one tab, so it is safe. `localStorage` is shared by
|
|
330
|
+
* every tab of the origin and has no atomic update: two tabs logging at the same
|
|
331
|
+
* moment can overwrite each other's entry while both get `ok: true`. In Chromium,
|
|
332
|
+
* tabs in separate processes (the usual case) even read a stale copy, so a lock
|
|
333
|
+
* reduces the loss without removing it. Where tabs may log at the same time and
|
|
334
|
+
* every entry matters, use `IndexedDbErrorAdapter`, whose writes are transactional.
|
|
213
335
|
*/
|
|
214
336
|
declare class WebStorageErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
215
337
|
private readonly storage;
|
|
216
338
|
private readonly key;
|
|
217
339
|
private readonly maxEntries;
|
|
218
340
|
private readonly prefix;
|
|
341
|
+
private readonly locks;
|
|
219
342
|
/** @throws {RangeError} if `options.maxEntries` is not a positive integer. */
|
|
220
343
|
constructor(storage: StorageLike, options?: WebStorageErrorOptions);
|
|
221
344
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
222
|
-
/**
|
|
345
|
+
/**
|
|
346
|
+
* Appends under a cross-tab lock when one is available. If the lock itself
|
|
347
|
+
* cannot be taken (for example a sandboxed frame), the entry is still written
|
|
348
|
+
* without it rather than lost.
|
|
349
|
+
*/
|
|
350
|
+
private appendLocked;
|
|
351
|
+
/** Reads, appends, trims, and writes back. Runs without awaiting, so calls in one tab never interleave. */
|
|
223
352
|
private append;
|
|
224
353
|
/** Returns the stored records, or none if the value is missing or unreadable. */
|
|
225
354
|
private readRecords;
|
|
355
|
+
/**
|
|
356
|
+
* Copies an unusable value to `<key>.corrupt` before it is replaced, so data
|
|
357
|
+
* the host app stored under the same key is not silently destroyed. Only the
|
|
358
|
+
* first one is kept; a later unusable value would not overwrite it.
|
|
359
|
+
*/
|
|
360
|
+
private setAside;
|
|
226
361
|
}
|
|
227
362
|
|
|
228
363
|
/**
|
|
@@ -281,7 +416,7 @@ declare class FlatFileErrorAdapter<TCode extends string = string> implements Err
|
|
|
281
416
|
private readonly prefix;
|
|
282
417
|
private fs;
|
|
283
418
|
private directoryReady;
|
|
284
|
-
private queue;
|
|
419
|
+
private readonly queue;
|
|
285
420
|
/** @throws {RangeError} if `maxBytes` or `maxFiles` is not a positive integer. */
|
|
286
421
|
constructor(filePath: string, options?: FlatFileErrorOptions);
|
|
287
422
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
@@ -334,7 +469,7 @@ declare class JsonFileErrorAdapter<TCode extends string = string> implements Err
|
|
|
334
469
|
private readonly prefix;
|
|
335
470
|
private fs;
|
|
336
471
|
private directoryReady;
|
|
337
|
-
private queue;
|
|
472
|
+
private readonly queue;
|
|
338
473
|
/** @throws {RangeError} if `maxEntries` or `indent` is invalid, or `maxEntries` is used outside array mode. */
|
|
339
474
|
constructor(filePath: string, options?: JsonFileErrorOptions);
|
|
340
475
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
@@ -371,6 +506,12 @@ interface SqlErrorOptions {
|
|
|
371
506
|
createTable?: boolean;
|
|
372
507
|
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
373
508
|
prefix?: string;
|
|
509
|
+
/**
|
|
510
|
+
* How long `log` waits for each database statement before giving up, in milliseconds. Defaults
|
|
511
|
+
* to 10000. Pass `Infinity` to wait without limit. The call cannot be
|
|
512
|
+
* cancelled, so it may still finish after `log` has returned a failure.
|
|
513
|
+
*/
|
|
514
|
+
timeoutMs?: number;
|
|
374
515
|
}
|
|
375
516
|
/**
|
|
376
517
|
* Returns the statements that create the error log table and its index for a
|
|
@@ -381,6 +522,12 @@ interface SqlErrorOptions {
|
|
|
381
522
|
* cause, and `DATETIME(3)` columns holding UTC times.
|
|
382
523
|
*/
|
|
383
524
|
declare function buildErrorLogTableDdl(dialect: SqlDialect, tableName?: string): string[];
|
|
525
|
+
/**
|
|
526
|
+
* Returns the statement that widens the `code` and `source` columns of a MySQL
|
|
527
|
+
* table created by `buildErrorLogTableDdl` in 0.4.0 or earlier, where they were
|
|
528
|
+
* `VARCHAR(255)` and a longer value made the insert fail. Run it once.
|
|
529
|
+
*/
|
|
530
|
+
declare function buildMySqlWidenColumnsSql(tableName?: string): string[];
|
|
384
531
|
/** Shared by the Postgres and MySQL adapters: only the SQL text and time format differ. */
|
|
385
532
|
declare abstract class SqlErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
386
533
|
private readonly client;
|
|
@@ -389,6 +536,7 @@ declare abstract class SqlErrorAdapter<TCode extends string = string> implements
|
|
|
389
536
|
private readonly tableName;
|
|
390
537
|
private readonly createTable;
|
|
391
538
|
private readonly prefix;
|
|
539
|
+
private readonly timeoutMs;
|
|
392
540
|
private tableReady;
|
|
393
541
|
protected constructor(client: SqlClientLike, dialect: SqlDialect, adapterName: string, options: SqlErrorOptions);
|
|
394
542
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
@@ -396,6 +544,8 @@ declare abstract class SqlErrorAdapter<TCode extends string = string> implements
|
|
|
396
544
|
/** Creates the table once. A failed attempt is retried on the next log call. */
|
|
397
545
|
private ensureTable;
|
|
398
546
|
private runCreateStatements;
|
|
547
|
+
/** Runs one statement, giving up waiting after `timeoutMs`. */
|
|
548
|
+
private query;
|
|
399
549
|
}
|
|
400
550
|
/**
|
|
401
551
|
* Writes entries to Postgres. Pass a `pg` `Pool` or `Client`.
|
|
@@ -454,6 +604,12 @@ interface RedisErrorOptions {
|
|
|
454
604
|
side?: 'left' | 'right';
|
|
455
605
|
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
456
606
|
prefix?: string;
|
|
607
|
+
/**
|
|
608
|
+
* How long `log` waits for each Redis command before giving up, in milliseconds. Defaults
|
|
609
|
+
* to 10000. Pass `Infinity` to wait without limit. The call cannot be
|
|
610
|
+
* cancelled, so it may still finish after `log` has returned a failure.
|
|
611
|
+
*/
|
|
612
|
+
timeoutMs?: number;
|
|
457
613
|
}
|
|
458
614
|
/**
|
|
459
615
|
* Writes entries to Redis, either as a capped list of JSON strings or as a
|
|
@@ -467,6 +623,7 @@ declare class RedisErrorAdapter<TCode extends string = string> implements ErrorL
|
|
|
467
623
|
private readonly maxEntries;
|
|
468
624
|
private readonly side;
|
|
469
625
|
private readonly prefix;
|
|
626
|
+
private readonly timeoutMs;
|
|
470
627
|
/** @throws {RangeError} if `key` is empty, `maxEntries` is not a positive integer, or `mode` / `side` is unknown. */
|
|
471
628
|
constructor(client: RedisClientLike, options?: RedisErrorOptions);
|
|
472
629
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
@@ -476,6 +633,8 @@ declare class RedisErrorAdapter<TCode extends string = string> implements ErrorL
|
|
|
476
633
|
* stored by now, so a trim failure is reported but does not fail the call;
|
|
477
634
|
* the next successful write trims the list again.
|
|
478
635
|
*/
|
|
636
|
+
/** Sends one command, giving up waiting after `timeoutMs`. */
|
|
637
|
+
private send;
|
|
479
638
|
private trimList;
|
|
480
639
|
}
|
|
481
640
|
|
|
@@ -492,6 +651,11 @@ interface S3PutObjectInput {
|
|
|
492
651
|
* it is, and so does anything else that follows the S3 API, including
|
|
493
652
|
* DigitalOcean Spaces, Cloudflare R2 and MinIO when you point the client at
|
|
494
653
|
* their endpoint.
|
|
654
|
+
*
|
|
655
|
+
* Some S3-compatible providers reject the checksum headers that recent AWS SDK
|
|
656
|
+
* v3 versions add by default. If uploads fail with a checksum or header error,
|
|
657
|
+
* create the client with `requestChecksumCalculation: 'WHEN_REQUIRED'` and
|
|
658
|
+
* `responseChecksumValidation: 'WHEN_REQUIRED'`.
|
|
495
659
|
*/
|
|
496
660
|
interface S3ClientLike {
|
|
497
661
|
putObject(input: S3PutObjectInput): Promise<unknown>;
|
|
@@ -523,6 +687,13 @@ interface S3ErrorOptions {
|
|
|
523
687
|
batch?: S3BatchOptions;
|
|
524
688
|
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
525
689
|
prefix?: string;
|
|
690
|
+
/**
|
|
691
|
+
* How long an upload may take before `log` (or `flush`) gives up waiting, in
|
|
692
|
+
* milliseconds. Defaults to 10000. Pass `Infinity` to wait without limit. The
|
|
693
|
+
* upload cannot be cancelled, so it may still finish afterwards; in batch mode
|
|
694
|
+
* the entries stay buffered and are uploaded again by the next flush.
|
|
695
|
+
*/
|
|
696
|
+
timeoutMs?: number;
|
|
526
697
|
}
|
|
527
698
|
/**
|
|
528
699
|
* Stores entries as objects in an S3-compatible bucket, keyed by date and UUID:
|
|
@@ -541,9 +712,10 @@ declare class S3ErrorAdapter<TCode extends string = string> implements ErrorLogA
|
|
|
541
712
|
private readonly contentType;
|
|
542
713
|
private readonly batch;
|
|
543
714
|
private readonly logPrefix;
|
|
715
|
+
private readonly timeoutMs;
|
|
544
716
|
private buffer;
|
|
545
717
|
private timer;
|
|
546
|
-
private flushQueue;
|
|
718
|
+
private readonly flushQueue;
|
|
547
719
|
/** @throws {RangeError} if `bucket` is empty or the batch options are invalid. */
|
|
548
720
|
constructor(client: S3ClientLike, bucket: string, options?: S3ErrorOptions);
|
|
549
721
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
@@ -559,4 +731,202 @@ declare class S3ErrorAdapter<TCode extends string = string> implements ErrorLogA
|
|
|
559
731
|
private fail;
|
|
560
732
|
}
|
|
561
733
|
|
|
562
|
-
|
|
734
|
+
/** The fields stored for each entry, in Firestore and in the Realtime Database. */
|
|
735
|
+
interface FirebaseErrorRecord {
|
|
736
|
+
/** UUID. In Firestore it is also the document id. */
|
|
737
|
+
id: string;
|
|
738
|
+
code: string;
|
|
739
|
+
message: string;
|
|
740
|
+
/** ISO 8601 timestamp of the entry. */
|
|
741
|
+
timestamp: string;
|
|
742
|
+
source?: string;
|
|
743
|
+
/**
|
|
744
|
+
* The entry's cause as a JSON string. Plain text avoids Firestore's limits on
|
|
745
|
+
* custom objects and nested arrays, and the Realtime Database's limits on key
|
|
746
|
+
* characters, so an odd cause can never make the write fail.
|
|
747
|
+
*/
|
|
748
|
+
cause?: string;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* The only thing the Firestore adapter needs from a collection: `doc(id).set(data)`.
|
|
752
|
+
* A `firebase-admin` collection (`getFirestore().collection('error_log')`) fits
|
|
753
|
+
* as it is. With the web SDK, wrap it:
|
|
754
|
+
*
|
|
755
|
+
* ```ts
|
|
756
|
+
* const collectionLike: FirestoreCollectionLike = {
|
|
757
|
+
* doc: (id) => ({ set: (data) => setDoc(doc(db, 'error_log', id), data) }),
|
|
758
|
+
* };
|
|
759
|
+
* ```
|
|
760
|
+
*/
|
|
761
|
+
interface FirestoreCollectionLike {
|
|
762
|
+
doc(documentId: string): {
|
|
763
|
+
set(data: FirebaseErrorRecord): Promise<unknown>;
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* The only thing the Realtime Database adapter needs from a reference:
|
|
768
|
+
* `push(value)`, which resolves once the write is saved. A `firebase-admin`
|
|
769
|
+
* reference (`getDatabase().ref('error_log')`) fits as it is. With the web SDK, wrap it:
|
|
770
|
+
*
|
|
771
|
+
* ```ts
|
|
772
|
+
* const refLike: RealtimeDatabaseRefLike = {
|
|
773
|
+
* push: (value) => push(ref(db, 'error_log'), value),
|
|
774
|
+
* child: (key) => ({ set: (value) => set(ref(db, `error_log/${key}`), value) }),
|
|
775
|
+
* };
|
|
776
|
+
* ```
|
|
777
|
+
*
|
|
778
|
+
* `child(key).set(value)` is only needed for entries that carry their own `id`
|
|
779
|
+
* (see `ErrorLogEntry.id`): they are written under that key, so logging the same
|
|
780
|
+
* entry again overwrites the record instead of adding a second one.
|
|
781
|
+
*/
|
|
782
|
+
interface RealtimeDatabaseRefLike {
|
|
783
|
+
push(value: FirebaseErrorRecord): PromiseLike<unknown>;
|
|
784
|
+
child?(key: string): {
|
|
785
|
+
set(value: FirebaseErrorRecord): PromiseLike<unknown>;
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
interface FirebaseErrorOptions {
|
|
789
|
+
/**
|
|
790
|
+
* How long `log` waits for the write to be saved before giving up, in
|
|
791
|
+
* milliseconds. Defaults to 10000. Pass `Infinity` to wait without limit.
|
|
792
|
+
*
|
|
793
|
+
* The Firebase SDKs keep writes queued while the connection is down, and
|
|
794
|
+
* their promises stay pending until it returns, so without a limit `log`
|
|
795
|
+
* could hang for as long as an outage lasts. A write that timed out is still
|
|
796
|
+
* queued by the SDK and may be saved later. Retry a timeout only with the same
|
|
797
|
+
* `ErrorLogEntry.id`, so the retry overwrites the first write instead of
|
|
798
|
+
* adding a second one.
|
|
799
|
+
*/
|
|
800
|
+
timeoutMs?: number;
|
|
801
|
+
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
802
|
+
prefix?: string;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Writes each entry as a document in a Firestore collection. The document id is
|
|
806
|
+
* the entry's UUID, so the SDK finishing one queued write twice cannot create two
|
|
807
|
+
* documents. Calling `log` again without an `id` on the entry is a new write
|
|
808
|
+
* with a new UUID: if a call reported a timeout, the first write may still be
|
|
809
|
+
* saved later, and logging the same entry again can store it twice. Give the
|
|
810
|
+
* entry an `id` and the retry overwrites the same document.
|
|
811
|
+
*/
|
|
812
|
+
declare class FirestoreErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
813
|
+
private readonly collection;
|
|
814
|
+
private readonly prefix;
|
|
815
|
+
private readonly timeoutMs;
|
|
816
|
+
/** @throws {RangeError} if `options.timeoutMs` is not a positive number or `Infinity`. */
|
|
817
|
+
constructor(collection: FirestoreCollectionLike, options?: FirebaseErrorOptions);
|
|
818
|
+
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Writes each entry under a Realtime Database reference with `push`, which
|
|
822
|
+
* generates a chronologically ordered key.
|
|
823
|
+
*/
|
|
824
|
+
declare class RealtimeDatabaseErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
825
|
+
private readonly reference;
|
|
826
|
+
private readonly prefix;
|
|
827
|
+
private readonly timeoutMs;
|
|
828
|
+
/** @throws {RangeError} if `options.timeoutMs` is not a positive number or `Infinity`. */
|
|
829
|
+
constructor(reference: RealtimeDatabaseRefLike, options?: FirebaseErrorOptions);
|
|
830
|
+
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/** The row inserted for each entry. The columns match the Postgres adapter's table. */
|
|
834
|
+
interface SupabaseErrorRow {
|
|
835
|
+
/** UUID primary key. */
|
|
836
|
+
id: string;
|
|
837
|
+
code: string;
|
|
838
|
+
message: string;
|
|
839
|
+
/** The cause as JSON, or `null` when the entry has none. */
|
|
840
|
+
cause: unknown;
|
|
841
|
+
source: string | null;
|
|
842
|
+
/** ISO 8601 timestamp of the entry. */
|
|
843
|
+
occurred_at: string;
|
|
844
|
+
}
|
|
845
|
+
/** The error object `supabase-js` returns (a `PostgrestError`) instead of throwing. */
|
|
846
|
+
interface SupabaseErrorLike {
|
|
847
|
+
message: string;
|
|
848
|
+
code?: string;
|
|
849
|
+
details?: string | null;
|
|
850
|
+
hint?: string | null;
|
|
851
|
+
}
|
|
852
|
+
/** What `insert` resolves to. The status fields are used when the error has no message. */
|
|
853
|
+
interface SupabaseInsertResult {
|
|
854
|
+
error: SupabaseErrorLike | null;
|
|
855
|
+
status?: number;
|
|
856
|
+
statusText?: string;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* The only thing the Supabase adapter needs from a client: `from(table).insert(row)`.
|
|
860
|
+
* A `supabase-js` client (`createClient(url, key)`) fits as it is.
|
|
861
|
+
*/
|
|
862
|
+
interface SupabaseClientLike {
|
|
863
|
+
from(table: string): {
|
|
864
|
+
insert(row: SupabaseErrorRow): PromiseLike<SupabaseInsertResult>;
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
interface SupabaseErrorOptions {
|
|
868
|
+
/** Table to write to. Defaults to `error_log`. Letters, digits and `_` only. */
|
|
869
|
+
tableName?: string;
|
|
870
|
+
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
871
|
+
prefix?: string;
|
|
872
|
+
/**
|
|
873
|
+
* How long `log` waits for Supabase to answer before giving up, in milliseconds. Defaults
|
|
874
|
+
* to 10000. Pass `Infinity` to wait without limit. The call cannot be
|
|
875
|
+
* cancelled, so it may still finish after `log` has returned a failure.
|
|
876
|
+
*/
|
|
877
|
+
timeoutMs?: number;
|
|
878
|
+
}
|
|
879
|
+
interface SupabaseSetupOptions {
|
|
880
|
+
/**
|
|
881
|
+
* Postgres roles allowed to insert through the API, for example
|
|
882
|
+
* `['authenticated']`. Leave empty when only a server using the service role
|
|
883
|
+
* key logs: that role bypasses row level security, so it needs no policy, and
|
|
884
|
+
* its key must never be shipped to a browser.
|
|
885
|
+
*
|
|
886
|
+
* The generated policy only checks the size of each field. It does not tie a
|
|
887
|
+
* row to a user, so any holder of an allowed role's key can still add entries
|
|
888
|
+
* of their own making: forged entries can hide real incidents, and a flood of
|
|
889
|
+
* them uses up your database quota. Prefer `authenticated` over `anon`.
|
|
890
|
+
*/
|
|
891
|
+
insertRoles?: string[];
|
|
892
|
+
/**
|
|
893
|
+
* Required to allow `anon` (or `public`) to insert. The `anon` key is part of
|
|
894
|
+
* your frontend, so this lets anyone who loads the page write to the log.
|
|
895
|
+
* Defaults to `false`.
|
|
896
|
+
*/
|
|
897
|
+
allowPublicInsert?: boolean;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Returns the SQL to run once in the Supabase SQL editor: the error log table,
|
|
901
|
+
* with row level security turned on (as Supabase recommends for every table in
|
|
902
|
+
* an exposed schema) and, optionally, an insert-only policy for the given roles.
|
|
903
|
+
* The policy limits the size of each field. Nobody but the service role can read
|
|
904
|
+
* the log through the API.
|
|
905
|
+
*
|
|
906
|
+
* @throws {RangeError} if the table name or a role is not a plain identifier, or
|
|
907
|
+
* `anon` / `public` is listed without `allowPublicInsert: true`.
|
|
908
|
+
*/
|
|
909
|
+
declare function buildSupabaseSetupSql(tableName?: string, options?: SupabaseSetupOptions): string[];
|
|
910
|
+
/**
|
|
911
|
+
* Writes entries to a Supabase table through `supabase-js`. Create the table
|
|
912
|
+
* first with `buildSupabaseSetupSql` (or `buildErrorLogTableDdl('postgres')`).
|
|
913
|
+
*
|
|
914
|
+
* ```ts
|
|
915
|
+
* const adapter = new SupabaseErrorAdapter(createClient(url, serviceRoleKey));
|
|
916
|
+
* ```
|
|
917
|
+
*
|
|
918
|
+
* `supabase-js` reports failures in a returned `error` field rather than
|
|
919
|
+
* throwing; the adapter turns both that and a thrown error into `{ ok: false }`.
|
|
920
|
+
*/
|
|
921
|
+
declare class SupabaseErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
922
|
+
private readonly client;
|
|
923
|
+
private readonly tableName;
|
|
924
|
+
private readonly prefix;
|
|
925
|
+
private readonly timeoutMs;
|
|
926
|
+
/** @throws {RangeError} if `options.tableName` is not a plain identifier or `options.timeoutMs` is invalid. */
|
|
927
|
+
constructor(client: SupabaseClientLike, options?: SupabaseErrorOptions);
|
|
928
|
+
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
929
|
+
private fail;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
export { type CollectionLike, CompositeErrorAdapter, type CompositeErrorOptions, 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 IdbRequestLike, type IdbTransactionLike, type IdbUpgradeDatabaseLike, IndexedDbErrorAdapter, type IndexedDbErrorOptions, type IndexedDbErrorRecord, JsonFileErrorAdapter, type JsonFileErrorOptions, type JsonFileErrorRecord, type LockManagerLike, 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, buildMySqlWidenColumnsSql, buildSupabaseSetupSql, createErrorLogStore, formatErrorLogLine, serializeCause, serializeEntry };
|