@siteping/adapter-drizzle 0.1.0 → 0.2.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 CHANGED
@@ -14,7 +14,7 @@
14
14
  npm install @siteping/adapter-drizzle @siteping/server drizzle-orm
15
15
  ```
16
16
 
17
- **Peer dependency:** `drizzle-orm` `>=0.45 <1` · Node ≥ 20.
17
+ **Peer dependency:** `drizzle-orm` `>=0.45 <1` · Node ≥ 22.
18
18
 
19
19
  ## Quick start
20
20
 
@@ -41,6 +41,13 @@ var StoreLimitError = class extends Error {
41
41
  this.name = "StoreLimitError";
42
42
  }
43
43
  };
44
+ var StoreValueTooLongError = class extends Error {
45
+ code = "STORE_VALUE_TOO_LONG";
46
+ constructor(message = "Value too long for the store", options) {
47
+ super(message, options);
48
+ this.name = "StoreValueTooLongError";
49
+ }
50
+ };
44
51
  function hasErrorCode(error, code) {
45
52
  return hasOwn(error, "code") && error.code === code;
46
53
  }
@@ -56,6 +63,14 @@ function isStorePersistence(error) {
56
63
  if (error instanceof StorePersistenceError) return true;
57
64
  return hasErrorCode(error, "STORE_PERSISTENCE");
58
65
  }
66
+ function isStoreLimit(error) {
67
+ if (error instanceof StoreLimitError) return true;
68
+ return hasErrorCode(error, "STORE_LIMIT");
69
+ }
70
+ function isStoreValueTooLong(error) {
71
+ if (error instanceof StoreValueTooLongError) return true;
72
+ return hasErrorCode(error, "STORE_VALUE_TOO_LONG");
73
+ }
59
74
 
60
75
  // ../core/src/concurrency.ts
61
76
  async function settleWithConcurrencyLimit(items, concurrency, task) {
@@ -92,6 +107,15 @@ function isUnreachableOffset(skip) {
92
107
  return !Number.isSafeInteger(skip);
93
108
  }
94
109
 
110
+ // ../core/src/logger.ts
111
+ function warnSafely(logger, message, context) {
112
+ try {
113
+ Promise.resolve(logger.warn(message, context)).catch(() => {
114
+ });
115
+ } catch {
116
+ }
117
+ }
118
+
95
119
  // ../core/src/screenshot-storage.ts
96
120
  var SCREENSHOT_DELETE_CONCURRENCY = 32;
97
121
  function screenshotMimeType(dataUrl) {
@@ -164,6 +188,26 @@ function buildFeedbackRecord(input, ctx) {
164
188
  };
165
189
  }
166
190
 
191
+ // ../core/src/text.ts
192
+ var UNSTORABLE_CODE_UNITS = /\0|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
193
+ var REPLACEMENT_CHARACTER = "\uFFFD";
194
+ function toStorableText(text) {
195
+ return text.replace(UNSTORABLE_CODE_UNITS, REPLACEMENT_CHARACTER);
196
+ }
197
+ function isPlainObject(value) {
198
+ if (typeof value !== "object" || value === null) return false;
199
+ const prototype = Object.getPrototypeOf(value);
200
+ return prototype === Object.prototype || prototype === null;
201
+ }
202
+ function toStorableValue(value) {
203
+ if (typeof value === "string") return toStorableText(value);
204
+ if (Array.isArray(value)) return value.map(toStorableValue);
205
+ if (!isPlainObject(value)) return value;
206
+ return Object.fromEntries(
207
+ Object.entries(value).map(([key, item]) => [toStorableText(key), toStorableValue(item)])
208
+ );
209
+ }
210
+
167
211
  // src/constants/errors.ts
168
212
  var DRIZZLE_STORE_MESSAGE_PREFIX = "[siteping] DrizzleStore";
169
213
  var DRIZZLE_QUERY_ERROR_MESSAGE_PREFIX = "Failed query:";
@@ -229,28 +273,6 @@ function withDriverErrors(gateway) {
229
273
  });
230
274
  }
231
275
 
232
- // src/constants/text.ts
233
- var UNSTORABLE_CODE_UNITS = /\0|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
234
- var REPLACEMENT_CHARACTER = "\uFFFD";
235
-
236
- // src/shared/text.ts
237
- function toStorableText(text) {
238
- return text.replace(UNSTORABLE_CODE_UNITS, REPLACEMENT_CHARACTER);
239
- }
240
- function isPlainObject(value) {
241
- if (typeof value !== "object" || value === null) return false;
242
- const prototype = Object.getPrototypeOf(value);
243
- return prototype === Object.prototype || prototype === null;
244
- }
245
- function toStorableValue(value) {
246
- if (typeof value === "string") return toStorableText(value);
247
- if (Array.isArray(value)) return value.map(toStorableValue);
248
- if (!isPlainObject(value)) return value;
249
- return Object.fromEntries(
250
- Object.entries(value).map(([key, item]) => [toStorableText(key), toStorableValue(item)])
251
- );
252
- }
253
-
254
276
  // src/shared/store.ts
255
277
  var silentLogger = {
256
278
  warn() {
@@ -303,8 +325,11 @@ var DrizzleSitepingStore = class {
303
325
  this.logger = options.logger ?? silentLogger;
304
326
  this.now = options.now ?? systemClock;
305
327
  }
328
+ /** Throws `StoreDuplicateError` when a feedback with the same `clientId` exists. */
306
329
  async createFeedback(data) {
307
- return (await this.createFeedbackIfAbsent(data)).feedback;
330
+ const { feedback, created } = await this.createFeedbackIfAbsent(data);
331
+ if (!created) throw new StoreDuplicateError(`A feedback with clientId ${data.clientId} already exists`);
332
+ return feedback;
308
333
  }
309
334
  /**
310
335
  * Insert the feedback unless a row with the same `clientId` exists. The
@@ -486,7 +511,15 @@ var DrizzleSitepingStore = class {
486
511
  * @throws `StoreLimitError` when a `client` comment meets a thread already holding `MAX_COMMENTS_PER_FEEDBACK` of them.
487
512
  * @throws `StorePersistenceError` when a database call fails.
488
513
  */
489
- async addComment(submittedFeedbackId, submitted) {
514
+ async addComment(feedbackId, data) {
515
+ return (await this.addCommentIfAbsent(feedbackId, data)).comment;
516
+ }
517
+ /**
518
+ * {@link addComment}, reporting whether this call inserted the comment: the
519
+ * single statement inserts at most one row per `clientId`, so of racing
520
+ * posts exactly one reports `created: true`.
521
+ */
522
+ async addCommentIfAbsent(submittedFeedbackId, submitted) {
490
523
  const feedbackId = toStorableText(submittedFeedbackId);
491
524
  const data = toStorableValue(submitted);
492
525
  const comment = buildCommentRecord(data, { id: crypto.randomUUID(), feedbackId, now: this.now() });
@@ -507,13 +540,13 @@ var DrizzleSitepingStore = class {
507
540
  if (feedbackGone) throw new StoreNotFoundError();
508
541
  throw error;
509
542
  }
510
- if (inserted) return comment;
543
+ if (inserted) return { comment, created: true };
511
544
  const replayed = await persistMutation(
512
545
  "addComment",
513
546
  identifiers,
514
547
  () => this.gateway.findCommentByClientId(data.clientId)
515
548
  );
516
- if (replayed) return replayed;
549
+ if (replayed) return { comment: replayed, created: false };
517
550
  const projectName = await persistMutation(
518
551
  "addComment",
519
552
  identifiers,
@@ -580,7 +613,8 @@ var DrizzleSitepingStore = class {
580
613
  });
581
614
  return url;
582
615
  } catch (error) {
583
- this.logger.warn(
616
+ warnSafely(
617
+ this.logger,
584
618
  "[siteping] DrizzleStore: screenshotStorage.upload failed \u2014 feedback saved without screenshot",
585
619
  { clientId, feedbackId, error }
586
620
  );
@@ -589,7 +623,8 @@ var DrizzleSitepingStore = class {
589
623
  }
590
624
  if (!this.inlineScreenshotWarned) {
591
625
  this.inlineScreenshotWarned = true;
592
- this.logger.warn(
626
+ warnSafely(
627
+ this.logger,
593
628
  "[siteping] DrizzleStore: no screenshotStorage configured \u2014 screenshots are stored inline as base64. Configure a ScreenshotStorage for production.",
594
629
  {}
595
630
  );
@@ -632,7 +667,8 @@ var DrizzleSitepingStore = class {
632
667
  try {
633
668
  referenced = await this.findReferencedScreenshotUrls(candidates);
634
669
  } catch (lookupError) {
635
- this.logger.warn(
670
+ warnSafely(
671
+ this.logger,
636
672
  `${DRIZZLE_STORE_MESSAGE_PREFIX}: screenshot references could not be checked \u2014 screenshots kept`,
637
673
  { ...context, screenshotUrls: candidates, error: lookupError }
638
674
  );
@@ -646,11 +682,15 @@ var DrizzleSitepingStore = class {
646
682
  );
647
683
  results.forEach((result, index) => {
648
684
  if (result.status === "rejected") {
649
- this.logger.warn(`${DRIZZLE_STORE_MESSAGE_PREFIX}: screenshotStorage.delete failed \u2014 object left in place`, {
650
- ...context,
651
- screenshotUrl: unreferenced[index],
652
- error: result.reason
653
- });
685
+ warnSafely(
686
+ this.logger,
687
+ `${DRIZZLE_STORE_MESSAGE_PREFIX}: screenshotStorage.delete failed \u2014 object left in place`,
688
+ {
689
+ ...context,
690
+ screenshotUrl: unreferenced[index],
691
+ error: result.reason
692
+ }
693
+ );
654
694
  }
655
695
  });
656
696
  }
@@ -792,7 +832,12 @@ export {
792
832
  StoreDuplicateError,
793
833
  StorePersistenceError,
794
834
  StoreLimitError,
835
+ StoreValueTooLongError,
836
+ isStoreNotFound,
837
+ isStoreDuplicate,
795
838
  isStorePersistence,
839
+ isStoreLimit,
840
+ isStoreValueTooLong,
796
841
  DRIZZLE_STORE_MESSAGE_PREFIX,
797
842
  DrizzleSitepingStore,
798
843
  CASE_INSENSITIVE_LIKE_OPERATOR,
@@ -811,4 +856,4 @@ export {
811
856
  selectValues,
812
857
  monotonicUpdatedAt
813
858
  };
814
- //# sourceMappingURL=chunk-6G6UAERF.js.map
859
+ //# sourceMappingURL=chunk-6DO75JHR.js.map