@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 +1 -1
- package/dist/{chunk-6G6UAERF.js → chunk-6DO75JHR.js} +80 -35
- package/dist/chunk-6DO75JHR.js.map +1 -0
- package/dist/deep-link.d.cts +10 -0
- package/dist/deep-link.d.ts +10 -0
- package/dist/errors.d.cts +46 -16
- package/dist/errors.d.ts +46 -16
- package/dist/i18n.d.cts +14 -10
- package/dist/i18n.d.ts +14 -10
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/libsql.cjs +86 -41
- package/dist/libsql.cjs.map +1 -1
- package/dist/libsql.d.cts +3 -2
- package/dist/libsql.d.ts +3 -2
- package/dist/libsql.js +12 -2
- package/dist/libsql.js.map +1 -1
- package/dist/logger.d.cts +30 -0
- package/dist/logger.d.ts +30 -0
- package/dist/pg.cjs +86 -41
- package/dist/pg.cjs.map +1 -1
- package/dist/pg.d.cts +3 -2
- package/dist/pg.d.ts +3 -2
- package/dist/pg.js +12 -2
- package/dist/pg.js.map +1 -1
- package/dist/schema.d.cts +31 -0
- package/dist/schema.d.ts +31 -0
- package/dist/screenshot-storage.d.cts +14 -17
- package/dist/screenshot-storage.d.ts +14 -17
- package/dist/siteping-core.d.cts +8 -5
- package/dist/siteping-core.d.ts +8 -5
- package/dist/store-helpers.d.cts +13 -9
- package/dist/store-helpers.d.ts +13 -9
- package/dist/text.d.cts +28 -0
- package/dist/text.d.ts +28 -0
- package/dist/types.d.cts +116 -39
- package/dist/types.d.ts +116 -39
- package/dist/wire.d.cts +6 -1
- package/dist/wire.d.ts +6 -1
- package/package.json +4 -4
- package/dist/chunk-6G6UAERF.js.map +0 -1
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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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)
|
|
14
|
+
* - HTTP 4xx (except 401/403/408/429) → `SitepingValidationError` (not retryable)
|
|
12
15
|
* - HTTP 401 / 403 → `SitepingAuthError` (not retryable)
|
|
13
|
-
* -
|
|
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`
|
|
19
|
-
* their own retry/queue/backoff
|
|
20
|
-
*
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
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
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
|
46
|
-
*
|
|
47
|
-
*
|
|
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 {
|
|
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:
|
|
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
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
|
46
|
-
*
|
|
47
|
-
*
|
|
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)
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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/
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
}
|