@siteping/widget 0.10.5 → 0.10.7

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.
Files changed (54) hide show
  1. package/README.md +25 -222
  2. package/dist/chunk-7ZBKJ6NW.js +1796 -0
  3. package/dist/chunk-7ZBKJ6NW.js.map +1 -0
  4. package/dist/chunk-FDQWFGRR.js +1796 -0
  5. package/dist/chunk-FDQWFGRR.js.map +1 -0
  6. package/dist/chunk-GKRKCSID.cjs +1796 -0
  7. package/dist/chunk-GKRKCSID.cjs.map +1 -0
  8. package/dist/chunk-NVNA5AI2.cjs +1796 -0
  9. package/dist/chunk-NVNA5AI2.cjs.map +1 -0
  10. package/dist/filters.d.cts +3 -1
  11. package/dist/filters.d.ts +3 -1
  12. package/dist/i18n.d.cts +67 -0
  13. package/dist/i18n.d.ts +67 -0
  14. package/dist/index.cjs +40 -40
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +152 -6
  17. package/dist/index.d.ts +152 -6
  18. package/dist/index.global.js +46 -46
  19. package/dist/index.global.js.map +1 -1
  20. package/dist/index.js +28 -28
  21. package/dist/index.js.map +1 -1
  22. package/dist/panel-7URHNKQH.js +2 -0
  23. package/dist/panel-7URHNKQH.js.map +1 -0
  24. package/dist/panel-FUPBUN6F.js +2 -0
  25. package/dist/panel-FUPBUN6F.js.map +1 -0
  26. package/dist/panel-M2QKMFYO.cjs +2 -0
  27. package/dist/panel-M2QKMFYO.cjs.map +1 -0
  28. package/dist/panel-XUTDVTAR.cjs +2 -0
  29. package/dist/panel-XUTDVTAR.cjs.map +1 -0
  30. package/dist/react.cjs +47 -47
  31. package/dist/react.cjs.map +1 -1
  32. package/dist/react.js +41 -41
  33. package/dist/react.js.map +1 -1
  34. package/dist/screenshot-storage.d.cts +5 -0
  35. package/dist/screenshot-storage.d.ts +5 -0
  36. package/dist/siteping-core.d.cts +8 -3
  37. package/dist/siteping-core.d.ts +8 -3
  38. package/dist/store-helpers.d.cts +87 -0
  39. package/dist/store-helpers.d.ts +87 -0
  40. package/dist/type-utils.d.cts +13 -44
  41. package/dist/type-utils.d.ts +13 -44
  42. package/dist/types.d.cts +147 -110
  43. package/dist/types.d.ts +147 -110
  44. package/dist/wire.d.cts +35 -0
  45. package/dist/wire.d.ts +35 -0
  46. package/package.json +2 -1
  47. package/dist/chunk-D2AVRF24.js +0 -1796
  48. package/dist/chunk-D2AVRF24.js.map +0 -1
  49. package/dist/chunk-GRKBXAGP.cjs +0 -1796
  50. package/dist/chunk-GRKBXAGP.cjs.map +0 -1
  51. package/dist/panel-LMVRKAQH.cjs +0 -2
  52. package/dist/panel-LMVRKAQH.cjs.map +0 -1
  53. package/dist/panel-OXUE7PJY.js +0 -2
  54. package/dist/panel-OXUE7PJY.js.map +0 -1
@@ -40,6 +40,11 @@ export interface ScreenshotStorage {
40
40
  *
41
41
  * Adapters call this synchronously inside `createFeedback` — keep it
42
42
  * fast or move to a queue if needed.
43
+ *
44
+ * **Security note:** `ctx.feedbackId` is the *client-generated* id
45
+ * (`clientId`) — the record id does not exist yet at upload time. Treat it
46
+ * as attacker-controlled: sanitize before using it in filesystem paths or
47
+ * object keys, even though server adapters validate its shape upstream.
43
48
  */
44
49
  upload(dataUrl: string, ctx: {
45
50
  feedbackId: string;
@@ -40,6 +40,11 @@ export interface ScreenshotStorage {
40
40
  *
41
41
  * Adapters call this synchronously inside `createFeedback` — keep it
42
42
  * fast or move to a queue if needed.
43
+ *
44
+ * **Security note:** `ctx.feedbackId` is the *client-generated* id
45
+ * (`clientId`) — the record id does not exist yet at upload time. Treat it
46
+ * as attacker-controlled: sanitize before using it in filesystem paths or
47
+ * object keys, even though server adapters validate its shape upstream.
43
48
  */
44
49
  upload(dataUrl: string, ctx: {
45
50
  feedbackId: string;
@@ -2,10 +2,15 @@ export type { SitepingErrorCode } from "./errors.cjs";
2
2
  export { SitepingAuthError, SitepingError, SitepingNetworkError, SitepingValidationError } from "./errors.cjs";
3
3
  export type { FilterResult } from "./filters.cjs";
4
4
  export { applyFeedbackFilters } from "./filters.cjs";
5
+ export type { I18n, LocaleLoaders, TranslateFunction } from "./i18n.cjs";
6
+ export { createI18n, interpolate, tWithParams } from "./i18n.cjs";
5
7
  export type { FieldDef, IndexDef, ModelDef, PrismaNativeType, PrismaScalarType, RelationDef, RelationKind, RelationOnDelete, SitepingModelFieldName, SitepingModelName, } from "./schema.cjs";
6
8
  export { isRelationField, isScalarField, SITEPING_MODELS } from "./schema.cjs";
7
9
  export type { ScreenshotStorage } from "./screenshot-storage.cjs";
8
- export type { AssertEqual, Brand, DeepReadonly, IfEquals, KeysOfType, NonEmptyArray, Prettify, Replace, RequiredNonNull, } from "./type-utils.cjs";
10
+ export type { CollectionStore, CollectionStoreBackend } from "./store-helpers.cjs";
11
+ export { buildAnnotationRecord, buildFeedbackRecord, createCollectionStore } from "./store-helpers.cjs";
12
+ export type { AssertEqual, IfEquals, Prettify, Serialized } from "./type-utils.cjs";
9
13
  export { hasOwn, isRecord } from "./type-utils.cjs";
10
- export type { AnchorData, AnnotationCreateInput, AnnotationPayload, AnnotationRecord, AnnotationResponse, BuiltinLocale, ConsoleDiagnosticEntry, ConsoleDiagnosticLevel, DiagnosticsCaptureOptions, DiagnosticsSnapshot, FeedbackCreateInput, FeedbackPage, FeedbackPayload, FeedbackQuery, FeedbackRecord, FeedbackResponse, FeedbackResponseList, FeedbackStatus, FeedbackType, FeedbackUpdateInput, NetworkDiagnosticEntry, PageScope, RectData, ScreenshotRegion, SitepingConfig, SitepingDeepLinkOptions, SitepingHeadersOption, SitepingIdentity, SitepingInstance, SitepingLocale, SitepingPosition, SitepingPublicEventListener, SitepingPublicEvents, SitepingSkipReason, SitepingStore, SitepingTheme, SitepingUnsubscribe, } from "./types.cjs";
11
- export { BUILTIN_LOCALES, CLOSED_FEEDBACK_STATUSES, FEEDBACK_STATUSES, FEEDBACK_TYPES, flattenAnnotation, isClosedStatus, isStoreDuplicate, isStoreNotFound, isStorePersistence, StoreDuplicateError, StoreNotFoundError, StorePersistenceError, } from "./types.cjs";
14
+ export type { AnchorData, AnnotationCreateInput, AnnotationPayload, AnnotationRecord, AnnotationResponse, BuiltinLocale, ClosedFeedbackStatus, ConsoleDiagnosticEntry, ConsoleDiagnosticLevel, DiagnosticsCaptureOptions, DiagnosticsSnapshot, FeedbackCreateInput, FeedbackPage, FeedbackPayload, FeedbackQuery, FeedbackRecord, FeedbackResponse, FeedbackResponseList, FeedbackStatus, FeedbackType, FeedbackUpdateInput, NetworkDiagnosticEntry, OpenFeedbackStatus, PageScope, RectData, ScreenshotRegion, SitepingBaseConfig, SitepingConfig, SitepingDeepLinkOptions, SitepingHeadersOption, SitepingHttpConfig, SitepingIdentity, SitepingInstance, SitepingLocale, SitepingPosition, SitepingPublicEventListener, SitepingPublicEvents, SitepingSkipReason, SitepingStore, SitepingStoreConfig, SitepingTheme, SitepingUnsubscribe, } from "./types.cjs";
15
+ export { BUILTIN_LOCALES, CLOSED_FEEDBACK_STATUSES, CONSOLE_DIAGNOSTIC_LEVELS, FEEDBACK_STATUSES, FEEDBACK_TYPES, flattenAnnotation, isClosedStatus, isStoreDuplicate, isStoreNotFound, isStorePersistence, OPEN_FEEDBACK_STATUSES, StoreDuplicateError, StoreNotFoundError, StorePersistenceError, toFeedbackUpdate, } from "./types.cjs";
16
+ export { errorFromResponse, feedbackQueryToSearchParams, networkErrorFromException } from "./wire.cjs";
@@ -2,10 +2,15 @@ export type { SitepingErrorCode } from "./errors.js";
2
2
  export { SitepingAuthError, SitepingError, SitepingNetworkError, SitepingValidationError } from "./errors.js";
3
3
  export type { FilterResult } from "./filters.js";
4
4
  export { applyFeedbackFilters } from "./filters.js";
5
+ export type { I18n, LocaleLoaders, TranslateFunction } from "./i18n.js";
6
+ export { createI18n, interpolate, tWithParams } from "./i18n.js";
5
7
  export type { FieldDef, IndexDef, ModelDef, PrismaNativeType, PrismaScalarType, RelationDef, RelationKind, RelationOnDelete, SitepingModelFieldName, SitepingModelName, } from "./schema.js";
6
8
  export { isRelationField, isScalarField, SITEPING_MODELS } from "./schema.js";
7
9
  export type { ScreenshotStorage } from "./screenshot-storage.js";
8
- export type { AssertEqual, Brand, DeepReadonly, IfEquals, KeysOfType, NonEmptyArray, Prettify, Replace, RequiredNonNull, } from "./type-utils.js";
10
+ export type { CollectionStore, CollectionStoreBackend } from "./store-helpers.js";
11
+ export { buildAnnotationRecord, buildFeedbackRecord, createCollectionStore } from "./store-helpers.js";
12
+ export type { AssertEqual, IfEquals, Prettify, Serialized } from "./type-utils.js";
9
13
  export { hasOwn, isRecord } from "./type-utils.js";
10
- export type { AnchorData, AnnotationCreateInput, AnnotationPayload, AnnotationRecord, AnnotationResponse, BuiltinLocale, ConsoleDiagnosticEntry, ConsoleDiagnosticLevel, DiagnosticsCaptureOptions, DiagnosticsSnapshot, FeedbackCreateInput, FeedbackPage, FeedbackPayload, FeedbackQuery, FeedbackRecord, FeedbackResponse, FeedbackResponseList, FeedbackStatus, FeedbackType, FeedbackUpdateInput, NetworkDiagnosticEntry, PageScope, RectData, ScreenshotRegion, SitepingConfig, SitepingDeepLinkOptions, SitepingHeadersOption, SitepingIdentity, SitepingInstance, SitepingLocale, SitepingPosition, SitepingPublicEventListener, SitepingPublicEvents, SitepingSkipReason, SitepingStore, SitepingTheme, SitepingUnsubscribe, } from "./types.js";
11
- export { BUILTIN_LOCALES, CLOSED_FEEDBACK_STATUSES, FEEDBACK_STATUSES, FEEDBACK_TYPES, flattenAnnotation, isClosedStatus, isStoreDuplicate, isStoreNotFound, isStorePersistence, StoreDuplicateError, StoreNotFoundError, StorePersistenceError, } from "./types.js";
14
+ export type { AnchorData, AnnotationCreateInput, AnnotationPayload, AnnotationRecord, AnnotationResponse, BuiltinLocale, ClosedFeedbackStatus, ConsoleDiagnosticEntry, ConsoleDiagnosticLevel, DiagnosticsCaptureOptions, DiagnosticsSnapshot, FeedbackCreateInput, FeedbackPage, FeedbackPayload, FeedbackQuery, FeedbackRecord, FeedbackResponse, FeedbackResponseList, FeedbackStatus, FeedbackType, FeedbackUpdateInput, NetworkDiagnosticEntry, OpenFeedbackStatus, PageScope, RectData, ScreenshotRegion, SitepingBaseConfig, SitepingConfig, SitepingDeepLinkOptions, SitepingHeadersOption, SitepingHttpConfig, SitepingIdentity, SitepingInstance, SitepingLocale, SitepingPosition, SitepingPublicEventListener, SitepingPublicEvents, SitepingSkipReason, SitepingStore, SitepingStoreConfig, SitepingTheme, SitepingUnsubscribe, } from "./types.js";
15
+ export { BUILTIN_LOCALES, CLOSED_FEEDBACK_STATUSES, CONSOLE_DIAGNOSTIC_LEVELS, FEEDBACK_STATUSES, FEEDBACK_TYPES, flattenAnnotation, isClosedStatus, isStoreDuplicate, isStoreNotFound, isStorePersistence, OPEN_FEEDBACK_STATUSES, StoreDuplicateError, StoreNotFoundError, StorePersistenceError, toFeedbackUpdate, } from "./types.js";
16
+ export { errorFromResponse, feedbackQueryToSearchParams, networkErrorFromException } from "./wire.js";
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Record-construction helpers and the collection-store engine.
3
+ *
4
+ * Every snapshot-style adapter (memory, localStorage, flat file, KV, …)
5
+ * needs the same three ingredients: turn a `FeedbackCreateInput` into a
6
+ * `FeedbackRecord` (null-normalizing optional fields, stamping ids and
7
+ * timestamps), filter/paginate with `applyFeedbackFilters`, and implement
8
+ * the dedup/update/delete choreography of the `SitepingStore` contract.
9
+ *
10
+ * `buildFeedbackRecord` / `buildAnnotationRecord` cover the first part for
11
+ * any adapter. `createCollectionStore` covers all of it: give it `load`,
12
+ * `persist`, and `generateId`, and it returns a fully conformant
13
+ * `SitepingStore` — writing a new snapshot adapter is ~20 lines plus its
14
+ * storage specifics.
15
+ */
16
+ import type { AnnotationCreateInput, AnnotationRecord, FeedbackCreateInput, FeedbackRecord, SitepingStore } from "./types.cjs";
17
+ /**
18
+ * Build a persisted `AnnotationRecord` from its create input — normalizes
19
+ * the optional anchor fields to `null` and stamps identity/timestamp.
20
+ */
21
+ export declare function buildAnnotationRecord(input: AnnotationCreateInput, ctx: {
22
+ id: string;
23
+ feedbackId: string;
24
+ now: Date;
25
+ }): AnnotationRecord;
26
+ /**
27
+ * Build a persisted `FeedbackRecord` (with its annotations) from a create
28
+ * input — normalizes every optional field to `null` and stamps ids and
29
+ * timestamps. Adapters without external screenshot storage keep the data
30
+ * URL inline on `screenshotUrl`, which is what this helper does; adapters
31
+ * with a `ScreenshotStorage` upload first and override `screenshotUrl`.
32
+ */
33
+ export declare function buildFeedbackRecord(input: FeedbackCreateInput, ctx: {
34
+ id: string;
35
+ annotationId: () => string;
36
+ now?: Date;
37
+ }): FeedbackRecord;
38
+ /**
39
+ * Storage primitives behind a collection store. `load`/`persist` may be
40
+ * sync or async — the engine awaits both, so in-memory arrays, localStorage
41
+ * and async KV stores all fit the same three functions.
42
+ */
43
+ export interface CollectionStoreBackend {
44
+ /** Return the current full snapshot of feedback records. */
45
+ load(): FeedbackRecord[] | Promise<FeedbackRecord[]>;
46
+ /**
47
+ * Persist the full snapshot. Throw `StorePersistenceError` when the write
48
+ * is lost (quota, storage disabled, …) — never swallow the failure.
49
+ */
50
+ persist(feedbacks: FeedbackRecord[]): void | Promise<void>;
51
+ /** Generate a unique id for a new feedback or annotation record. */
52
+ generateId(): string;
53
+ }
54
+ /**
55
+ * A `SitepingStore` with the optional `verifyProjectOwnership` guaranteed —
56
+ * what `createCollectionStore` returns.
57
+ */
58
+ export type CollectionStore = SitepingStore & Required<Pick<SitepingStore, "verifyProjectOwnership">>;
59
+ /**
60
+ * Build a fully conformant `SitepingStore` on top of a snapshot backend.
61
+ *
62
+ * The engine implements the whole store contract: clientId dedup (idempotent
63
+ * create), newest-first ordering, the standard filter/pagination pipeline,
64
+ * `StoreNotFoundError` on missing update/delete, project-scoped bulk delete,
65
+ * and `verifyProjectOwnership`. When `persist` fails during `createFeedback`
66
+ * and the record carries an inline screenshot, the engine retries once
67
+ * without the screenshot (by far the heaviest field) so the text feedback
68
+ * survives a storage-quota hit; if that also fails, the error propagates —
69
+ * returning the record would claim a success that was never persisted.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * export class MemoryStore implements SitepingStore {
74
+ * private feedbacks: FeedbackRecord[] = [];
75
+ * private readonly store = createCollectionStore({
76
+ * load: () => this.feedbacks,
77
+ * persist: (next) => {
78
+ * this.feedbacks = next;
79
+ * },
80
+ * generateId: () => crypto.randomUUID(),
81
+ * });
82
+ * createFeedback = this.store.createFeedback;
83
+ * // …delegate the remaining methods the same way
84
+ * }
85
+ * ```
86
+ */
87
+ export declare function createCollectionStore(backend: CollectionStoreBackend): CollectionStore;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Record-construction helpers and the collection-store engine.
3
+ *
4
+ * Every snapshot-style adapter (memory, localStorage, flat file, KV, …)
5
+ * needs the same three ingredients: turn a `FeedbackCreateInput` into a
6
+ * `FeedbackRecord` (null-normalizing optional fields, stamping ids and
7
+ * timestamps), filter/paginate with `applyFeedbackFilters`, and implement
8
+ * the dedup/update/delete choreography of the `SitepingStore` contract.
9
+ *
10
+ * `buildFeedbackRecord` / `buildAnnotationRecord` cover the first part for
11
+ * any adapter. `createCollectionStore` covers all of it: give it `load`,
12
+ * `persist`, and `generateId`, and it returns a fully conformant
13
+ * `SitepingStore` — writing a new snapshot adapter is ~20 lines plus its
14
+ * storage specifics.
15
+ */
16
+ import type { AnnotationCreateInput, AnnotationRecord, FeedbackCreateInput, FeedbackRecord, SitepingStore } from "./types.js";
17
+ /**
18
+ * Build a persisted `AnnotationRecord` from its create input — normalizes
19
+ * the optional anchor fields to `null` and stamps identity/timestamp.
20
+ */
21
+ export declare function buildAnnotationRecord(input: AnnotationCreateInput, ctx: {
22
+ id: string;
23
+ feedbackId: string;
24
+ now: Date;
25
+ }): AnnotationRecord;
26
+ /**
27
+ * Build a persisted `FeedbackRecord` (with its annotations) from a create
28
+ * input — normalizes every optional field to `null` and stamps ids and
29
+ * timestamps. Adapters without external screenshot storage keep the data
30
+ * URL inline on `screenshotUrl`, which is what this helper does; adapters
31
+ * with a `ScreenshotStorage` upload first and override `screenshotUrl`.
32
+ */
33
+ export declare function buildFeedbackRecord(input: FeedbackCreateInput, ctx: {
34
+ id: string;
35
+ annotationId: () => string;
36
+ now?: Date;
37
+ }): FeedbackRecord;
38
+ /**
39
+ * Storage primitives behind a collection store. `load`/`persist` may be
40
+ * sync or async — the engine awaits both, so in-memory arrays, localStorage
41
+ * and async KV stores all fit the same three functions.
42
+ */
43
+ export interface CollectionStoreBackend {
44
+ /** Return the current full snapshot of feedback records. */
45
+ load(): FeedbackRecord[] | Promise<FeedbackRecord[]>;
46
+ /**
47
+ * Persist the full snapshot. Throw `StorePersistenceError` when the write
48
+ * is lost (quota, storage disabled, …) — never swallow the failure.
49
+ */
50
+ persist(feedbacks: FeedbackRecord[]): void | Promise<void>;
51
+ /** Generate a unique id for a new feedback or annotation record. */
52
+ generateId(): string;
53
+ }
54
+ /**
55
+ * A `SitepingStore` with the optional `verifyProjectOwnership` guaranteed —
56
+ * what `createCollectionStore` returns.
57
+ */
58
+ export type CollectionStore = SitepingStore & Required<Pick<SitepingStore, "verifyProjectOwnership">>;
59
+ /**
60
+ * Build a fully conformant `SitepingStore` on top of a snapshot backend.
61
+ *
62
+ * The engine implements the whole store contract: clientId dedup (idempotent
63
+ * create), newest-first ordering, the standard filter/pagination pipeline,
64
+ * `StoreNotFoundError` on missing update/delete, project-scoped bulk delete,
65
+ * and `verifyProjectOwnership`. When `persist` fails during `createFeedback`
66
+ * and the record carries an inline screenshot, the engine retries once
67
+ * without the screenshot (by far the heaviest field) so the text feedback
68
+ * survives a storage-quota hit; if that also fails, the error propagates —
69
+ * returning the record would claim a success that was never persisted.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * export class MemoryStore implements SitepingStore {
74
+ * private feedbacks: FeedbackRecord[] = [];
75
+ * private readonly store = createCollectionStore({
76
+ * load: () => this.feedbacks,
77
+ * persist: (next) => {
78
+ * this.feedbacks = next;
79
+ * },
80
+ * generateId: () => crypto.randomUUID(),
81
+ * });
82
+ * createFeedback = this.store.createFeedback;
83
+ * // …delegate the remaining methods the same way
84
+ * }
85
+ * ```
86
+ */
87
+ export declare function createCollectionStore(backend: CollectionStoreBackend): CollectionStore;
@@ -17,61 +17,30 @@
17
17
  export type Prettify<T> = {
18
18
  [K in keyof T]: T[K];
19
19
  } & {};
20
- /**
21
- * Branded primitive — opaque alias safer than a raw `string` for IDs and
22
- * other opaque tokens. Cast through `as` at the construction boundary.
23
- *
24
- * @example
25
- * type FeedbackId = Brand<string, "FeedbackId">;
26
- * const id = "abc" as FeedbackId;
27
- */
28
- export type Brand<T, B extends string> = T & {
29
- readonly __brand: B;
30
- };
31
- /**
32
- * Tuple type for arrays guaranteed to contain at least one element. Useful
33
- * for invariants enforced at the type system level.
34
- */
35
- export type NonEmptyArray<T> = readonly [T, ...T[]];
36
20
  /**
37
21
  * Returns `Y` when `A` is exactly assignable to `B` and vice-versa,
38
22
  * otherwise `N`. Powers compile-time equality assertions.
39
23
  */
40
24
  export type IfEquals<A, B, Y = true, N = false> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? Y : N;
41
25
  /**
42
- * Compile-time exact-type guard — throws a type error when `Actual` and
43
- * `Expected` drift apart. Use inside dead-code `void` statements to lock
44
- * inferred types against manually-written interfaces.
26
+ * Compile-time exact-type guard — resolves to `true` when `Actual` and
27
+ * `Expected` are identical, `never` otherwise. Assign the result to a
28
+ * `const _lock: AssertEqual<A, B> = true;` so any drift becomes a compile
29
+ * error at the declaration site.
45
30
  */
46
31
  export type AssertEqual<Actual, Expected> = IfEquals<Actual, Expected, true, never>;
47
32
  /**
48
- * Extract literal keys whose values match the predicate type `V`.
49
- * Useful to derive enums or maps from a config record.
50
- */
51
- export type KeysOfType<T, V> = {
52
- [K in keyof T]-?: T[K] extends V ? K : never;
53
- }[keyof T];
54
- /**
55
- * Mark every property of `T` as required AND non-nullable. Stricter than
56
- * `Required<T>` because it also strips `null`.
33
+ * JSON-serialized shape of `T` — the wire form produced by `Response.json()`
34
+ * / `JSON.stringify`: `Date` becomes ISO `string` (nullability preserved),
35
+ * arrays are serialized element-wise, everything else is untouched.
36
+ *
37
+ * Used to derive the `*Response` API types from the `*Record` store types so
38
+ * the two can never drift: add a field to `FeedbackRecord` and
39
+ * `FeedbackResponse` follows automatically.
57
40
  */
58
- export type RequiredNonNull<T> = {
59
- [K in keyof T]-?: NonNullable<T[K]>;
41
+ export type Serialized<T> = {
42
+ [K in keyof T]: T[K] extends Date ? string : T[K] extends Date | null ? string | null : T[K] extends (infer U)[] ? Serialized<U>[] : T[K];
60
43
  };
61
- /**
62
- * Replace properties of `T` whose names are in `K` with the shape `R[K]`.
63
- * Preserves the rest. Cheaper than chaining `Omit` + `Pick`.
64
- */
65
- export type Replace<T, K extends keyof T, R extends {
66
- [P in K]: unknown;
67
- }> = Prettify<Omit<T, K> & R>;
68
- /**
69
- * Deep readonly — recurses through arrays and plain objects, leaves
70
- * primitives, classes, and built-ins (Date, Map, Set, …) untouched.
71
- */
72
- export type DeepReadonly<T> = T extends (infer U)[] ? ReadonlyArray<DeepReadonly<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepReadonly<U>> : T extends object ? T extends (...args: never[]) => unknown ? T : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | Promise<unknown> ? T : {
73
- readonly [K in keyof T]: DeepReadonly<T[K]>;
74
- } : T;
75
44
  /**
76
45
  * Type guard that narrows `value` to a non-null `Record<PropertyKey, unknown>`.
77
46
  * Useful when validating arbitrary inputs before reading fields.
@@ -17,61 +17,30 @@
17
17
  export type Prettify<T> = {
18
18
  [K in keyof T]: T[K];
19
19
  } & {};
20
- /**
21
- * Branded primitive — opaque alias safer than a raw `string` for IDs and
22
- * other opaque tokens. Cast through `as` at the construction boundary.
23
- *
24
- * @example
25
- * type FeedbackId = Brand<string, "FeedbackId">;
26
- * const id = "abc" as FeedbackId;
27
- */
28
- export type Brand<T, B extends string> = T & {
29
- readonly __brand: B;
30
- };
31
- /**
32
- * Tuple type for arrays guaranteed to contain at least one element. Useful
33
- * for invariants enforced at the type system level.
34
- */
35
- export type NonEmptyArray<T> = readonly [T, ...T[]];
36
20
  /**
37
21
  * Returns `Y` when `A` is exactly assignable to `B` and vice-versa,
38
22
  * otherwise `N`. Powers compile-time equality assertions.
39
23
  */
40
24
  export type IfEquals<A, B, Y = true, N = false> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? Y : N;
41
25
  /**
42
- * Compile-time exact-type guard — throws a type error when `Actual` and
43
- * `Expected` drift apart. Use inside dead-code `void` statements to lock
44
- * inferred types against manually-written interfaces.
26
+ * Compile-time exact-type guard — resolves to `true` when `Actual` and
27
+ * `Expected` are identical, `never` otherwise. Assign the result to a
28
+ * `const _lock: AssertEqual<A, B> = true;` so any drift becomes a compile
29
+ * error at the declaration site.
45
30
  */
46
31
  export type AssertEqual<Actual, Expected> = IfEquals<Actual, Expected, true, never>;
47
32
  /**
48
- * Extract literal keys whose values match the predicate type `V`.
49
- * Useful to derive enums or maps from a config record.
50
- */
51
- export type KeysOfType<T, V> = {
52
- [K in keyof T]-?: T[K] extends V ? K : never;
53
- }[keyof T];
54
- /**
55
- * Mark every property of `T` as required AND non-nullable. Stricter than
56
- * `Required<T>` because it also strips `null`.
33
+ * JSON-serialized shape of `T` — the wire form produced by `Response.json()`
34
+ * / `JSON.stringify`: `Date` becomes ISO `string` (nullability preserved),
35
+ * arrays are serialized element-wise, everything else is untouched.
36
+ *
37
+ * Used to derive the `*Response` API types from the `*Record` store types so
38
+ * the two can never drift: add a field to `FeedbackRecord` and
39
+ * `FeedbackResponse` follows automatically.
57
40
  */
58
- export type RequiredNonNull<T> = {
59
- [K in keyof T]-?: NonNullable<T[K]>;
41
+ export type Serialized<T> = {
42
+ [K in keyof T]: T[K] extends Date ? string : T[K] extends Date | null ? string | null : T[K] extends (infer U)[] ? Serialized<U>[] : T[K];
60
43
  };
61
- /**
62
- * Replace properties of `T` whose names are in `K` with the shape `R[K]`.
63
- * Preserves the rest. Cheaper than chaining `Omit` + `Pick`.
64
- */
65
- export type Replace<T, K extends keyof T, R extends {
66
- [P in K]: unknown;
67
- }> = Prettify<Omit<T, K> & R>;
68
- /**
69
- * Deep readonly — recurses through arrays and plain objects, leaves
70
- * primitives, classes, and built-ins (Date, Map, Set, …) untouched.
71
- */
72
- export type DeepReadonly<T> = T extends (infer U)[] ? ReadonlyArray<DeepReadonly<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepReadonly<U>> : T extends object ? T extends (...args: never[]) => unknown ? T : T extends Date | RegExp | Map<unknown, unknown> | Set<unknown> | Promise<unknown> ? T : {
73
- readonly [K in keyof T]: DeepReadonly<T[K]>;
74
- } : T;
75
44
  /**
76
45
  * Type guard that narrows `value` to a non-null `Record<PropertyKey, unknown>`.
77
46
  * Useful when validating arbitrary inputs before reading fields.