@siteping/adapter-drizzle 0.1.0 → 0.2.1

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/errors.d.ts CHANGED
@@ -1,30 +1,30 @@
1
1
  /**
2
2
  * Typed error hierarchy for Siteping client/server boundaries.
3
3
  *
4
- * Consumers can `instanceof`-check or read `code` / `retryable` instead of
5
- * pattern-matching error messages. Designed to be additive on top of the
6
- * existing store errors (`StoreNotFoundError`, `StoreDuplicateError`) which
7
- * remain the canonical signals for server-side store implementations.
4
+ * Consumers match an error with a guard — `isSitepingError`,
5
+ * `isSitepingConfigError`, a store guard such as `isStoreNotFound` — then
6
+ * read `code` / `retryable` instead of pattern-matching messages. The guards
7
+ * are structural, never `instanceof`: every published package bundles its
8
+ * own copy of these classes (tsup `noExternal`), so an error one package
9
+ * throws fails `instanceof` against another's class. The widget and the
10
+ * dashboard export the guard and the types, not the classes.
8
11
  *
9
12
  * Usage on the widget side (api-client.ts):
10
13
  * - fetch failures / aborts / timeouts → `SitepingNetworkError` (retryable)
11
- * - HTTP 4xx (except 401/403) → `SitepingValidationError` (not retryable)
14
+ * - HTTP 4xx (except 401/403/408/429) → `SitepingValidationError` (not retryable)
12
15
  * - HTTP 401 / 403 → `SitepingAuthError` (not retryable)
13
- * - everything else → `SitepingError` generic
16
+ * - HTTP 5xx / 408 / 429 → `SitepingError` code `"SERVER"` (retryable)
14
17
  *
15
18
  * Store mode (store-client.ts): a `createFeedback` that does not settle in
16
- * time → `SitepingError` with code `"TIMEOUT"` (retryable).
19
+ * time → `SitepingError` with code `"TIMEOUT"` (retryable); a reply to a
20
+ * store that keeps no comments → `"SERVER"`, not retryable.
17
21
  *
18
- * `retryable` is meta information surfaced to host apps that want to wire
19
- * their own retry/queue/backoff strategy — the widget already retries
20
- * network failures via its built-in retry queue.
22
+ * `retryable` says whether the same request may succeed later, unchanged —
23
+ * meta information for host apps that wire their own retry/queue/backoff
24
+ * strategy; the widget already retries and queues transient failures itself.
21
25
  */
22
- /**
23
- * Discriminant string carried by every `SitepingError`. Subclasses pin a
24
- * literal value; the base class accepts a wider string so userland can
25
- * extend the hierarchy without colliding with built-ins.
26
- */
27
- export type SitepingErrorCode = "NETWORK" | "VALIDATION" | "AUTH" | "SERVER" | "TIMEOUT" | (string & {});
26
+ /** Discriminant string carried by every `SitepingError`. */
27
+ export type SitepingErrorCode = "NETWORK" | "VALIDATION" | "AUTH" | "SERVER" | "TIMEOUT";
28
28
  export declare class SitepingError<TCode extends SitepingErrorCode = SitepingErrorCode> extends Error {
29
29
  readonly code: TCode;
30
30
  readonly retryable: boolean;
@@ -53,3 +53,33 @@ export declare class SitepingAuthError extends SitepingError<"AUTH"> {
53
53
  readonly status: 401 | 403;
54
54
  constructor(message: string, status: 401 | 403);
55
55
  }
56
+ /**
57
+ * Every error the widget and the dashboard report for a failed call, told
58
+ * apart by `code`: narrowing on it reaches a subclass's own fields, such as
59
+ * an `AUTH` error's `status`.
60
+ */
61
+ export type SitepingClientError = SitepingNetworkError | SitepingValidationError | SitepingAuthError | SitepingError<"SERVER" | "TIMEOUT">;
62
+ /**
63
+ * Whether `error` is a `SitepingError` (any subclass) — matched on its
64
+ * fields, a known `code` and a boolean `retryable`, so it holds for an error
65
+ * another package's copy of the class threw. Anything else reaching an
66
+ * `onError` (a store's own error, a panel action's) fails it.
67
+ */
68
+ export declare function isSitepingError(error: unknown): error is SitepingClientError;
69
+ /** Stable codes of the errors a handler factory throws for options it cannot run with. */
70
+ export type SitepingConfigErrorCode = "CONFIG_STORE_MISSING" | "CONFIG_MAX_BODY_BYTES_INVALID" | "CONFIG_OWNERSHIP_CHECK_MISSING" | "CONFIG_WEBHOOK_TIMEOUT_INVALID" | "CONFIG_API_KEY_REQUIRED";
71
+ /**
72
+ * Thrown by `createSitepingHandler` while it is created, never per request,
73
+ * when its options cannot run safely. The message says how to fix it; branch
74
+ * on `code`, after {@link isSitepingConfigError}.
75
+ */
76
+ export declare class SitepingConfigError extends Error {
77
+ readonly code: SitepingConfigErrorCode;
78
+ constructor(code: SitepingConfigErrorCode, message: string);
79
+ }
80
+ /**
81
+ * Whether `error` is a `SitepingConfigError` — matched on its `name` and a
82
+ * `CONFIG_` code rather than `instanceof`, for the same cross-bundle reason
83
+ * as {@link isSitepingError}.
84
+ */
85
+ export declare function isSitepingConfigError(error: unknown): error is SitepingConfigError;
package/dist/i18n.d.cts CHANGED
@@ -12,7 +12,7 @@
12
12
  * dictionary file) exists. Adding a locale cannot silently fall back to
13
13
  * English anymore.
14
14
  */
15
- import { type BuiltinLocale } from "./types.cjs";
15
+ import type { BuiltinLocale } from "./types.cjs";
16
16
  /**
17
17
  * Lazy dictionary loaders for every non-English built-in locale. Use static
18
18
  * `() => import("./xx.cjs").then((m) => m.xx)` thunks — bundlers keep
@@ -26,7 +26,8 @@ export interface I18n<T> {
26
26
  /**
27
27
  * Create a translation function for the given locale.
28
28
  *
29
- * Locale resolution: exact language match > English fallback, per key.
29
+ * Locale resolution, per key: `registerLocale` override > built-in
30
+ * dictionary of the language > English fallback.
30
31
  * Non-English built-in locales are lazy-loaded via `loadLocale` — call
31
32
  * `await loadLocale(locale)` at init if you want the UI to render in the
32
33
  * target language immediately; until the dictionary lands, keys resolve
@@ -34,17 +35,20 @@ export interface I18n<T> {
34
35
  */
35
36
  createT(locale: string): TranslateFunction<T>;
36
37
  /**
37
- * Dynamically import a built-in locale and register it. Returns the
38
- * loaded dictionary, or `null` if the locale isn't a known built-in.
39
- * Custom locales registered via `registerLocale` bypass this loader —
40
- * they are already in the registry (and are returned as registered, so a
41
- * partial custom dictionary comes back partial).
38
+ * Dynamically import a built-in locale (once) and register it. Returns the
39
+ * locale as `createT` resolves it — the built-in dictionary with any
40
+ * `registerLocale` overrides on top — or `null` for a code that is neither
41
+ * built in nor registered. A custom code never hits a loader: it comes
42
+ * back as registered, so a partial custom dictionary comes back partial.
42
43
  */
43
44
  loadLocale(locale: string): Promise<Partial<T> | null>;
44
45
  /**
45
- * Register a custom locale at runtime. Partial dictionaries are welcome —
46
- * missing keys fall back to English per key, so overriding a single
47
- * string never requires copying the whole catalog.
46
+ * Register translations at runtime, for a new locale or over a built-in
47
+ * one. Partial dictionaries are welcome: the keys given override the
48
+ * built-in dictionary (whenever it loads), every other key keeps its
49
+ * built-in value, then falls back to English — so overriding a single
50
+ * string never requires copying the whole catalog. Calls for the same
51
+ * code accumulate.
48
52
  */
49
53
  registerLocale(code: string, translations: Partial<T>): void;
50
54
  }
package/dist/i18n.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * dictionary file) exists. Adding a locale cannot silently fall back to
13
13
  * English anymore.
14
14
  */
15
- import { type BuiltinLocale } from "./types.js";
15
+ import type { BuiltinLocale } from "./types.js";
16
16
  /**
17
17
  * Lazy dictionary loaders for every non-English built-in locale. Use static
18
18
  * `() => import("./xx.js").then((m) => m.xx)` thunks — bundlers keep
@@ -26,7 +26,8 @@ export interface I18n<T> {
26
26
  /**
27
27
  * Create a translation function for the given locale.
28
28
  *
29
- * Locale resolution: exact language match > English fallback, per key.
29
+ * Locale resolution, per key: `registerLocale` override > built-in
30
+ * dictionary of the language > English fallback.
30
31
  * Non-English built-in locales are lazy-loaded via `loadLocale` — call
31
32
  * `await loadLocale(locale)` at init if you want the UI to render in the
32
33
  * target language immediately; until the dictionary lands, keys resolve
@@ -34,17 +35,20 @@ export interface I18n<T> {
34
35
  */
35
36
  createT(locale: string): TranslateFunction<T>;
36
37
  /**
37
- * Dynamically import a built-in locale and register it. Returns the
38
- * loaded dictionary, or `null` if the locale isn't a known built-in.
39
- * Custom locales registered via `registerLocale` bypass this loader —
40
- * they are already in the registry (and are returned as registered, so a
41
- * partial custom dictionary comes back partial).
38
+ * Dynamically import a built-in locale (once) and register it. Returns the
39
+ * locale as `createT` resolves it — the built-in dictionary with any
40
+ * `registerLocale` overrides on top — or `null` for a code that is neither
41
+ * built in nor registered. A custom code never hits a loader: it comes
42
+ * back as registered, so a partial custom dictionary comes back partial.
42
43
  */
43
44
  loadLocale(locale: string): Promise<Partial<T> | null>;
44
45
  /**
45
- * Register a custom locale at runtime. Partial dictionaries are welcome —
46
- * missing keys fall back to English per key, so overriding a single
47
- * string never requires copying the whole catalog.
46
+ * Register translations at runtime, for a new locale or over a built-in
47
+ * one. Partial dictionaries are welcome: the keys given override the
48
+ * built-in dictionary (whenever it loads), every other key keeps its
49
+ * built-in value, then falls back to English — so overriding a single
50
+ * string never requires copying the whole catalog. Calls for the same
51
+ * code accumulate.
48
52
  */
49
53
  registerLocale(code: string, translations: Partial<T>): void;
50
54
  }
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { SitepingStore, ScreenshotStorage } from './siteping-core.cjs';
1
+ import { SitepingStore, SitepingWarnLogger, ScreenshotStorage } from './siteping-core.cjs';
2
2
  export { FeedbackRecord, ScreenshotStorage, SitepingStore } from './siteping-core.cjs';
3
3
 
4
4
  /** Table names — override to fit your naming or avoid collisions. */
@@ -12,16 +12,16 @@ declare const DEFAULT_SITEPING_TABLE_NAMES: SitepingTableNames;
12
12
  /**
13
13
  * The store returned by the dialect factories — the full contract, including
14
14
  * the ownership check, the atomic `createFeedbackIfAbsent` and discussion
15
- * threads.
15
+ * threads with the atomic `addCommentIfAbsent`.
16
16
  */
17
- type DrizzleStore = SitepingStore & Required<Pick<SitepingStore, "verifyProjectOwnership" | "createFeedbackIfAbsent" | "addComment" | "deleteComment">>;
17
+ type DrizzleStore = SitepingStore & Required<Pick<SitepingStore, "verifyProjectOwnership" | "createFeedbackIfAbsent" | "addComment" | "addCommentIfAbsent" | "deleteComment">>;
18
18
  /**
19
19
  * Where the store reports degraded-but-non-fatal situations (failed
20
- * screenshot uploads or cleanups, inline screenshots). `console` satisfies it.
20
+ * screenshot uploads or cleanups, inline screenshots) — SitePing's shared
21
+ * logger, with `warn`: `console` satisfies it, and so does the logger handed
22
+ * to the server. A logger that throws or rejects never fails the store.
21
23
  */
22
- interface DrizzleStoreLogger {
23
- warn(message: string, context: Record<string, unknown>): void;
24
- }
24
+ type DrizzleStoreLogger = SitepingWarnLogger;
25
25
  interface DrizzleStoreOptions {
26
26
  /**
27
27
  * Upload screenshots to external storage (S3, R2, Cloudflare Images…) and
@@ -42,4 +42,5 @@ interface DrizzleStoreOptions {
42
42
  now?: (() => Date) | undefined;
43
43
  }
44
44
 
45
- export { DEFAULT_SITEPING_TABLE_NAMES, type DrizzleStore, type DrizzleStoreLogger, type DrizzleStoreOptions, type SitepingTableNames };
45
+ export { DEFAULT_SITEPING_TABLE_NAMES };
46
+ export type { DrizzleStore, DrizzleStoreLogger, DrizzleStoreOptions, SitepingTableNames };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SitepingStore, ScreenshotStorage } from './siteping-core.js';
1
+ import { SitepingStore, SitepingWarnLogger, ScreenshotStorage } from './siteping-core.js';
2
2
  export { FeedbackRecord, ScreenshotStorage, SitepingStore } from './siteping-core.js';
3
3
 
4
4
  /** Table names — override to fit your naming or avoid collisions. */
@@ -12,16 +12,16 @@ declare const DEFAULT_SITEPING_TABLE_NAMES: SitepingTableNames;
12
12
  /**
13
13
  * The store returned by the dialect factories — the full contract, including
14
14
  * the ownership check, the atomic `createFeedbackIfAbsent` and discussion
15
- * threads.
15
+ * threads with the atomic `addCommentIfAbsent`.
16
16
  */
17
- type DrizzleStore = SitepingStore & Required<Pick<SitepingStore, "verifyProjectOwnership" | "createFeedbackIfAbsent" | "addComment" | "deleteComment">>;
17
+ type DrizzleStore = SitepingStore & Required<Pick<SitepingStore, "verifyProjectOwnership" | "createFeedbackIfAbsent" | "addComment" | "addCommentIfAbsent" | "deleteComment">>;
18
18
  /**
19
19
  * Where the store reports degraded-but-non-fatal situations (failed
20
- * screenshot uploads or cleanups, inline screenshots). `console` satisfies it.
20
+ * screenshot uploads or cleanups, inline screenshots) — SitePing's shared
21
+ * logger, with `warn`: `console` satisfies it, and so does the logger handed
22
+ * to the server. A logger that throws or rejects never fails the store.
21
23
  */
22
- interface DrizzleStoreLogger {
23
- warn(message: string, context: Record<string, unknown>): void;
24
- }
24
+ type DrizzleStoreLogger = SitepingWarnLogger;
25
25
  interface DrizzleStoreOptions {
26
26
  /**
27
27
  * Upload screenshots to external storage (S3, R2, Cloudflare Images…) and
@@ -42,4 +42,5 @@ interface DrizzleStoreOptions {
42
42
  now?: (() => Date) | undefined;
43
43
  }
44
44
 
45
- export { DEFAULT_SITEPING_TABLE_NAMES, type DrizzleStore, type DrizzleStoreLogger, type DrizzleStoreOptions, type SitepingTableNames };
45
+ export { DEFAULT_SITEPING_TABLE_NAMES };
46
+ export type { DrizzleStore, DrizzleStoreLogger, DrizzleStoreOptions, SitepingTableNames };
package/dist/libsql.cjs CHANGED
@@ -25,9 +25,14 @@ __export(libsql_exports, {
25
25
  StoreLimitError: () => StoreLimitError,
26
26
  StoreNotFoundError: () => StoreNotFoundError,
27
27
  StorePersistenceError: () => StorePersistenceError,
28
+ StoreValueTooLongError: () => StoreValueTooLongError,
28
29
  createLibSQLSitepingStore: () => createLibSQLSitepingStore,
29
30
  createSitepingSqliteTables: () => createSitepingSqliteTables,
30
- isStorePersistence: () => isStorePersistence
31
+ isStoreDuplicate: () => isStoreDuplicate,
32
+ isStoreLimit: () => isStoreLimit,
33
+ isStoreNotFound: () => isStoreNotFound,
34
+ isStorePersistence: () => isStorePersistence,
35
+ isStoreValueTooLong: () => isStoreValueTooLong
31
36
  });
32
37
  module.exports = __toCommonJS(libsql_exports);
33
38
  var import_drizzle_orm9 = require("drizzle-orm");
@@ -82,6 +87,14 @@ async function settleWithConcurrencyLimit(items, concurrency, task) {
82
87
  return results;
83
88
  }
84
89
 
90
+ // ../core/src/type-utils.ts
91
+ function isRecord(value) {
92
+ return typeof value === "object" && value !== null;
93
+ }
94
+ function hasOwn(value, key) {
95
+ return isRecord(value) && key in value;
96
+ }
97
+
85
98
  // ../core/src/filters.ts
86
99
  var DEFAULT_PAGE_LIMIT = 50;
87
100
  var MAX_PAGE_LIMIT = 100;
@@ -97,12 +110,13 @@ function isUnreachableOffset(skip) {
97
110
  return !Number.isSafeInteger(skip);
98
111
  }
99
112
 
100
- // ../core/src/type-utils.ts
101
- function isRecord(value) {
102
- return typeof value === "object" && value !== null;
103
- }
104
- function hasOwn(value, key) {
105
- return isRecord(value) && key in value;
113
+ // ../core/src/logger.ts
114
+ function warnSafely(logger, message, context) {
115
+ try {
116
+ Promise.resolve(logger.warn(message, context)).catch(() => {
117
+ });
118
+ } catch {
119
+ }
106
120
  }
107
121
 
108
122
  // ../core/src/types.ts
@@ -135,6 +149,13 @@ var StoreLimitError = class extends Error {
135
149
  this.name = "StoreLimitError";
136
150
  }
137
151
  };
152
+ var StoreValueTooLongError = class extends Error {
153
+ code = "STORE_VALUE_TOO_LONG";
154
+ constructor(message = "Value too long for the store", options) {
155
+ super(message, options);
156
+ this.name = "StoreValueTooLongError";
157
+ }
158
+ };
138
159
  function hasErrorCode(error, code) {
139
160
  return hasOwn(error, "code") && error.code === code;
140
161
  }
@@ -150,6 +171,14 @@ function isStorePersistence(error) {
150
171
  if (error instanceof StorePersistenceError) return true;
151
172
  return hasErrorCode(error, "STORE_PERSISTENCE");
152
173
  }
174
+ function isStoreLimit(error) {
175
+ if (error instanceof StoreLimitError) return true;
176
+ return hasErrorCode(error, "STORE_LIMIT");
177
+ }
178
+ function isStoreValueTooLong(error) {
179
+ if (error instanceof StoreValueTooLongError) return true;
180
+ return hasErrorCode(error, "STORE_VALUE_TOO_LONG");
181
+ }
153
182
 
154
183
  // ../core/src/screenshot-storage.ts
155
184
  var SCREENSHOT_DELETE_CONCURRENCY = 32;
@@ -223,6 +252,26 @@ function buildFeedbackRecord(input, ctx) {
223
252
  };
224
253
  }
225
254
 
255
+ // ../core/src/text.ts
256
+ var UNSTORABLE_CODE_UNITS = /\0|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
257
+ var REPLACEMENT_CHARACTER = "\uFFFD";
258
+ function toStorableText(text2) {
259
+ return text2.replace(UNSTORABLE_CODE_UNITS, REPLACEMENT_CHARACTER);
260
+ }
261
+ function isPlainObject(value) {
262
+ if (typeof value !== "object" || value === null) return false;
263
+ const prototype = Object.getPrototypeOf(value);
264
+ return prototype === Object.prototype || prototype === null;
265
+ }
266
+ function toStorableValue(value) {
267
+ if (typeof value === "string") return toStorableText(value);
268
+ if (Array.isArray(value)) return value.map(toStorableValue);
269
+ if (!isPlainObject(value)) return value;
270
+ return Object.fromEntries(
271
+ Object.entries(value).map(([key, item]) => [toStorableText(key), toStorableValue(item)])
272
+ );
273
+ }
274
+
226
275
  // src/constants/deletes.ts
227
276
  var PROJECT_DELETE_CHUNK_SIZE = 500;
228
277
 
@@ -264,28 +313,6 @@ function withDriverErrors(gateway) {
264
313
  });
265
314
  }
266
315
 
267
- // src/constants/text.ts
268
- var UNSTORABLE_CODE_UNITS = /\0|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
269
- var REPLACEMENT_CHARACTER = "\uFFFD";
270
-
271
- // src/shared/text.ts
272
- function toStorableText(text2) {
273
- return text2.replace(UNSTORABLE_CODE_UNITS, REPLACEMENT_CHARACTER);
274
- }
275
- function isPlainObject(value) {
276
- if (typeof value !== "object" || value === null) return false;
277
- const prototype = Object.getPrototypeOf(value);
278
- return prototype === Object.prototype || prototype === null;
279
- }
280
- function toStorableValue(value) {
281
- if (typeof value === "string") return toStorableText(value);
282
- if (Array.isArray(value)) return value.map(toStorableValue);
283
- if (!isPlainObject(value)) return value;
284
- return Object.fromEntries(
285
- Object.entries(value).map(([key, item]) => [toStorableText(key), toStorableValue(item)])
286
- );
287
- }
288
-
289
316
  // src/shared/store.ts
290
317
  var silentLogger = {
291
318
  warn() {
@@ -338,8 +365,11 @@ var DrizzleSitepingStore = class {
338
365
  this.logger = options.logger ?? silentLogger;
339
366
  this.now = options.now ?? systemClock;
340
367
  }
368
+ /** Throws `StoreDuplicateError` when a feedback with the same `clientId` exists. */
341
369
  async createFeedback(data) {
342
- return (await this.createFeedbackIfAbsent(data)).feedback;
370
+ const { feedback, created } = await this.createFeedbackIfAbsent(data);
371
+ if (!created) throw new StoreDuplicateError(`A feedback with clientId ${data.clientId} already exists`);
372
+ return feedback;
343
373
  }
344
374
  /**
345
375
  * Insert the feedback unless a row with the same `clientId` exists. The
@@ -521,7 +551,15 @@ var DrizzleSitepingStore = class {
521
551
  * @throws `StoreLimitError` when a `client` comment meets a thread already holding `MAX_COMMENTS_PER_FEEDBACK` of them.
522
552
  * @throws `StorePersistenceError` when a database call fails.
523
553
  */
524
- async addComment(submittedFeedbackId, submitted) {
554
+ async addComment(feedbackId, data) {
555
+ return (await this.addCommentIfAbsent(feedbackId, data)).comment;
556
+ }
557
+ /**
558
+ * {@link addComment}, reporting whether this call inserted the comment: the
559
+ * single statement inserts at most one row per `clientId`, so of racing
560
+ * posts exactly one reports `created: true`.
561
+ */
562
+ async addCommentIfAbsent(submittedFeedbackId, submitted) {
525
563
  const feedbackId = toStorableText(submittedFeedbackId);
526
564
  const data = toStorableValue(submitted);
527
565
  const comment = buildCommentRecord(data, { id: crypto.randomUUID(), feedbackId, now: this.now() });
@@ -542,13 +580,13 @@ var DrizzleSitepingStore = class {
542
580
  if (feedbackGone) throw new StoreNotFoundError();
543
581
  throw error;
544
582
  }
545
- if (inserted) return comment;
583
+ if (inserted) return { comment, created: true };
546
584
  const replayed = await persistMutation(
547
585
  "addComment",
548
586
  identifiers,
549
587
  () => this.gateway.findCommentByClientId(data.clientId)
550
588
  );
551
- if (replayed) return replayed;
589
+ if (replayed) return { comment: replayed, created: false };
552
590
  const projectName = await persistMutation(
553
591
  "addComment",
554
592
  identifiers,
@@ -615,7 +653,8 @@ var DrizzleSitepingStore = class {
615
653
  });
616
654
  return url;
617
655
  } catch (error) {
618
- this.logger.warn(
656
+ warnSafely(
657
+ this.logger,
619
658
  "[siteping] DrizzleStore: screenshotStorage.upload failed \u2014 feedback saved without screenshot",
620
659
  { clientId, feedbackId, error }
621
660
  );
@@ -624,7 +663,8 @@ var DrizzleSitepingStore = class {
624
663
  }
625
664
  if (!this.inlineScreenshotWarned) {
626
665
  this.inlineScreenshotWarned = true;
627
- this.logger.warn(
666
+ warnSafely(
667
+ this.logger,
628
668
  "[siteping] DrizzleStore: no screenshotStorage configured \u2014 screenshots are stored inline as base64. Configure a ScreenshotStorage for production.",
629
669
  {}
630
670
  );
@@ -667,7 +707,8 @@ var DrizzleSitepingStore = class {
667
707
  try {
668
708
  referenced = await this.findReferencedScreenshotUrls(candidates);
669
709
  } catch (lookupError) {
670
- this.logger.warn(
710
+ warnSafely(
711
+ this.logger,
671
712
  `${DRIZZLE_STORE_MESSAGE_PREFIX}: screenshot references could not be checked \u2014 screenshots kept`,
672
713
  { ...context, screenshotUrls: candidates, error: lookupError }
673
714
  );
@@ -681,11 +722,15 @@ var DrizzleSitepingStore = class {
681
722
  );
682
723
  results.forEach((result, index2) => {
683
724
  if (result.status === "rejected") {
684
- this.logger.warn(`${DRIZZLE_STORE_MESSAGE_PREFIX}: screenshotStorage.delete failed \u2014 object left in place`, {
685
- ...context,
686
- screenshotUrl: unreferenced[index2],
687
- error: result.reason
688
- });
725
+ warnSafely(
726
+ this.logger,
727
+ `${DRIZZLE_STORE_MESSAGE_PREFIX}: screenshotStorage.delete failed \u2014 object left in place`,
728
+ {
729
+ ...context,
730
+ screenshotUrl: unreferenced[index2],
731
+ error: result.reason
732
+ }
733
+ );
689
734
  }
690
735
  });
691
736
  }