@siteping/widget 0.10.10 → 0.11.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.
Files changed (122) hide show
  1. package/README.md +5 -4
  2. package/dist/chunk-CVWZCWBQ.js +9 -0
  3. package/dist/chunk-CVWZCWBQ.js.map +1 -0
  4. package/dist/chunk-CX5YE4V3.js +9 -0
  5. package/dist/chunk-CX5YE4V3.js.map +1 -0
  6. package/dist/chunk-KCOX2NRI.cjs +9 -0
  7. package/dist/chunk-KCOX2NRI.cjs.map +1 -0
  8. package/dist/chunk-QW2L5ARZ.cjs +9 -0
  9. package/dist/chunk-QW2L5ARZ.cjs.map +1 -0
  10. package/dist/concurrency.d.cts +14 -0
  11. package/dist/concurrency.d.ts +14 -0
  12. package/dist/de-73CTH2E4.cjs +2 -0
  13. package/dist/de-73CTH2E4.cjs.map +1 -0
  14. package/dist/de-GUUJUHYP.js +2 -0
  15. package/dist/de-GUUJUHYP.js.map +1 -0
  16. package/dist/deep-link.d.cts +14 -0
  17. package/dist/deep-link.d.ts +14 -0
  18. package/dist/email.d.cts +6 -3
  19. package/dist/email.d.ts +6 -3
  20. package/dist/errors.d.cts +18 -4
  21. package/dist/errors.d.ts +18 -4
  22. package/dist/{es-HEVFJPRI.js → es-3MXMSDUC.js} +2 -2
  23. package/dist/es-3MXMSDUC.js.map +1 -0
  24. package/dist/{es-5XQA3TPT.cjs → es-WJZ3XARH.cjs} +2 -2
  25. package/dist/es-WJZ3XARH.cjs.map +1 -0
  26. package/dist/filters.d.cts +31 -7
  27. package/dist/filters.d.ts +31 -7
  28. package/dist/fr-52F3ZCLQ.js +2 -0
  29. package/dist/fr-52F3ZCLQ.js.map +1 -0
  30. package/dist/fr-ZCRIJWI3.cjs +2 -0
  31. package/dist/fr-ZCRIJWI3.cjs.map +1 -0
  32. package/dist/i18n.d.cts +18 -0
  33. package/dist/i18n.d.ts +18 -0
  34. package/dist/index.cjs +3 -1588
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +12 -1
  37. package/dist/index.d.ts +12 -1
  38. package/dist/index.global.js +4 -3389
  39. package/dist/index.global.js.map +1 -1
  40. package/dist/index.js +3 -1588
  41. package/dist/index.js.map +1 -1
  42. package/dist/it-MEUT3RCC.js +2 -0
  43. package/dist/it-MEUT3RCC.js.map +1 -0
  44. package/dist/it-TGUTW6MO.cjs +2 -0
  45. package/dist/it-TGUTW6MO.cjs.map +1 -0
  46. package/dist/ja-DOS7AWLW.cjs +2 -0
  47. package/dist/ja-DOS7AWLW.cjs.map +1 -0
  48. package/dist/ja-U7NRNGPQ.js +2 -0
  49. package/dist/ja-U7NRNGPQ.js.map +1 -0
  50. package/dist/panel-HGYK7HVK.cjs +2 -0
  51. package/dist/panel-HGYK7HVK.cjs.map +1 -0
  52. package/dist/panel-RE2IHZ4S.js +2 -0
  53. package/dist/panel-RE2IHZ4S.js.map +1 -0
  54. package/dist/panel-RHSIY6KS.cjs +2 -0
  55. package/dist/panel-RHSIY6KS.cjs.map +1 -0
  56. package/dist/panel-XVIF2WMU.js +2 -0
  57. package/dist/panel-XVIF2WMU.js.map +1 -0
  58. package/dist/pt-BZRBHCYN.cjs +2 -0
  59. package/dist/pt-BZRBHCYN.cjs.map +1 -0
  60. package/dist/pt-MOS5DVOG.js +2 -0
  61. package/dist/pt-MOS5DVOG.js.map +1 -0
  62. package/dist/react.cjs +3 -1588
  63. package/dist/react.cjs.map +1 -1
  64. package/dist/react.js +3 -1588
  65. package/dist/react.js.map +1 -1
  66. package/dist/ru-KDNRHHNZ.cjs +2 -0
  67. package/dist/ru-KDNRHHNZ.cjs.map +1 -0
  68. package/dist/ru-VSWB5NKK.js +2 -0
  69. package/dist/ru-VSWB5NKK.js.map +1 -0
  70. package/dist/schema.d.cts +54 -0
  71. package/dist/schema.d.ts +54 -0
  72. package/dist/screenshot-storage.d.cts +61 -19
  73. package/dist/screenshot-storage.d.ts +61 -19
  74. package/dist/siteping-core.d.cts +9 -6
  75. package/dist/siteping-core.d.ts +9 -6
  76. package/dist/store-helpers.d.cts +59 -15
  77. package/dist/store-helpers.d.ts +59 -15
  78. package/dist/type-utils.d.cts +8 -0
  79. package/dist/type-utils.d.ts +8 -0
  80. package/dist/types.d.cts +399 -33
  81. package/dist/types.d.ts +399 -33
  82. package/dist/wire.d.cts +35 -2
  83. package/dist/wire.d.ts +35 -2
  84. package/package.json +3 -1
  85. package/dist/chunk-AFLWWD6M.cjs +0 -1796
  86. package/dist/chunk-AFLWWD6M.cjs.map +0 -1
  87. package/dist/chunk-ATTI2TS4.js +0 -1796
  88. package/dist/chunk-ATTI2TS4.js.map +0 -1
  89. package/dist/chunk-BP5TH2CD.js +0 -1796
  90. package/dist/chunk-BP5TH2CD.js.map +0 -1
  91. package/dist/chunk-OSADAHGS.cjs +0 -1796
  92. package/dist/chunk-OSADAHGS.cjs.map +0 -1
  93. package/dist/de-AZUOIGF2.cjs +0 -2
  94. package/dist/de-AZUOIGF2.cjs.map +0 -1
  95. package/dist/de-MAGIJBFC.js +0 -2
  96. package/dist/de-MAGIJBFC.js.map +0 -1
  97. package/dist/es-5XQA3TPT.cjs.map +0 -1
  98. package/dist/es-HEVFJPRI.js.map +0 -1
  99. package/dist/fr-ERYSAH32.cjs +0 -2
  100. package/dist/fr-ERYSAH32.cjs.map +0 -1
  101. package/dist/fr-OM2PHLDH.js +0 -2
  102. package/dist/fr-OM2PHLDH.js.map +0 -1
  103. package/dist/it-NXAZR4K6.cjs +0 -2
  104. package/dist/it-NXAZR4K6.cjs.map +0 -1
  105. package/dist/it-OQVYHKKV.js +0 -2
  106. package/dist/it-OQVYHKKV.js.map +0 -1
  107. package/dist/panel-2FEECS2N.cjs +0 -2
  108. package/dist/panel-2FEECS2N.cjs.map +0 -1
  109. package/dist/panel-CSFFA5FF.cjs +0 -2
  110. package/dist/panel-CSFFA5FF.cjs.map +0 -1
  111. package/dist/panel-S2UR5BSD.js +0 -2
  112. package/dist/panel-S2UR5BSD.js.map +0 -1
  113. package/dist/panel-SGSUHR7V.js +0 -2
  114. package/dist/panel-SGSUHR7V.js.map +0 -1
  115. package/dist/pt-CODDT6VY.cjs +0 -2
  116. package/dist/pt-CODDT6VY.cjs.map +0 -1
  117. package/dist/pt-VUUNL42H.js +0 -2
  118. package/dist/pt-VUUNL42H.js.map +0 -1
  119. package/dist/ru-72WUEADV.cjs +0 -2
  120. package/dist/ru-72WUEADV.cjs.map +0 -1
  121. package/dist/ru-NDWY7Y36.js +0 -2
  122. package/dist/ru-NDWY7Y36.js.map +0 -1
@@ -7,13 +7,13 @@
7
7
  * timestamps), filter/paginate with `applyFeedbackFilters`, and implement
8
8
  * the dedup/update/delete choreography of the `SitepingStore` contract.
9
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.
10
+ * `buildFeedbackRecord` / `buildAnnotationRecord` / `buildCommentRecord`
11
+ * cover the first part for any adapter. `createCollectionStore` covers all
12
+ * of it: give it `load`, `persist`, and `generateId`, and it returns a fully
13
+ * conformant `SitepingStore` — writing a new snapshot adapter is ~20 lines
14
+ * plus its storage specifics.
15
15
  */
16
- import type { AnnotationCreateInput, AnnotationRecord, FeedbackCreateInput, FeedbackRecord, SitepingStore } from "./types.cjs";
16
+ import type { AnnotationCreateInput, AnnotationRecord, CommentCreateInput, CommentRecord, FeedbackCreateInput, FeedbackRecord, SitepingStore } from "./types.cjs";
17
17
  /**
18
18
  * Build a persisted `AnnotationRecord` from its create input — normalizes
19
19
  * the optional anchor fields to `null` and stamps identity/timestamp.
@@ -23,12 +23,20 @@ export declare function buildAnnotationRecord(input: AnnotationCreateInput, ctx:
23
23
  feedbackId: string;
24
24
  now: Date;
25
25
  }): AnnotationRecord;
26
+ /** Build a persisted `CommentRecord` from its create input — stamps identity, thread and timestamp. */
27
+ export declare function buildCommentRecord(input: CommentCreateInput, ctx: {
28
+ id: string;
29
+ feedbackId: string;
30
+ now?: Date;
31
+ }): CommentRecord;
26
32
  /**
27
33
  * Build a persisted `FeedbackRecord` (with its annotations) from a create
28
34
  * input — normalizes every optional field to `null` and stamps ids and
29
35
  * timestamps. Adapters without external screenshot storage keep the data
30
36
  * URL inline on `screenshotUrl`, which is what this helper does; adapters
31
37
  * with a `ScreenshotStorage` upload first and override `screenshotUrl`.
38
+ * It leaves `comments` out, so the record minus its `annotations` stays an
39
+ * insertable feedback row; a store with threads returns `comments: []` itself.
32
40
  */
33
41
  export declare function buildFeedbackRecord(input: FeedbackCreateInput, ctx: {
34
42
  id: string;
@@ -37,8 +45,11 @@ export declare function buildFeedbackRecord(input: FeedbackCreateInput, ctx: {
37
45
  }): FeedbackRecord;
38
46
  /**
39
47
  * 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.
48
+ * sync or async, so in-memory arrays, localStorage and async KV stores all
49
+ * fit the same three functions. When both are sync, a mutation runs from
50
+ * `load` to `persist` without yielding, so no other code in the realm can
51
+ * write in between; an async backend is only serialized against the
52
+ * engine's own queue.
42
53
  */
43
54
  export interface CollectionStoreBackend {
44
55
  /**
@@ -55,28 +66,57 @@ export interface CollectionStoreBackend {
55
66
  * update.
56
67
  */
57
68
  persist(feedbacks: FeedbackRecord[]): void | Promise<void>;
58
- /** Generate a unique id for a new feedback or annotation record. */
69
+ /** Generate a unique id for a new feedback, annotation or comment record. */
59
70
  generateId(): string;
71
+ /**
72
+ * Keep discussion threads on the records: the store gains `addComment` and
73
+ * `deleteComment`, and new records start with `comments: []`. Opt in once
74
+ * `load` hands back what `persist` wrote for them, each comment's
75
+ * `createdAt` a `Date` again (a JSON backend revives it like the record's
76
+ * own dates). Off by default, so an adapter written before threads never
77
+ * gains them — over storage never written for them — on an engine update.
78
+ */
79
+ comments?: boolean | undefined;
60
80
  }
61
81
  /**
62
82
  * A `SitepingStore` with the optional `verifyProjectOwnership` guaranteed —
63
- * what `createCollectionStore` returns.
83
+ * what `createCollectionStore` returns, which also guarantees
84
+ * `createFeedbackIfAbsent`.
64
85
  */
65
86
  export type CollectionStore = SitepingStore & Required<Pick<SitepingStore, "verifyProjectOwnership">>;
66
87
  /**
67
88
  * Build a fully conformant `SitepingStore` on top of a snapshot backend.
68
89
  *
69
90
  * The engine implements the whole store contract: clientId dedup (idempotent
70
- * create), newest-first ordering, the standard filter/pagination pipeline,
71
- * `StoreNotFoundError` on missing update/delete, project-scoped bulk delete,
72
- * and `verifyProjectOwnership`. The snapshot returned by `load` is never
73
- * mutated: every write hands `persist` a new array, so a failed write leaves
91
+ * create, with `createFeedbackIfAbsent` reporting inserts), newest-first
92
+ * ordering, the standard filter/pagination pipeline, `StoreNotFoundError` on
93
+ * missing update/delete, project-scoped bulk delete,
94
+ * `verifyProjectOwnership`, and — with `comments: true` — discussion threads
95
+ * (`addComment`, `deleteComment`) kept on each record. The snapshot returned by `load` is
96
+ * never mutated: every write hands `persist` a new array, so a failed write leaves
74
97
  * a cached snapshot exactly as it was. When `persist` fails during `createFeedback`
75
98
  * and the record carries an inline screenshot, the engine retries once
76
99
  * without the screenshot (by far the heaviest field) so the text feedback
77
100
  * survives a storage-quota hit; if that also fails, the error propagates —
78
101
  * returning the record would claim a success that was never persisted.
79
102
  *
103
+ * Mutations (`createFeedbackIfAbsent`, `createFeedback`, `updateFeedback`,
104
+ * `deleteFeedback`, `deleteAllFeedbacks`, `addComment`, `deleteComment`) run
105
+ * one at a time through a queue owned by the returned store, so concurrent
106
+ * calls — the widget's `Promise.all` bulk resolve/delete, a comment posted
107
+ * while its feedback is resolved — never start from the same snapshot and
108
+ * overwrite each other, and `createFeedbackIfAbsent` reports `created: true`
109
+ * exactly once per `clientId`. A failed mutation rejects with its own error
110
+ * and does not block the ones queued after it, but `load` and `persist` must
111
+ * always settle: one that never does stalls every later mutation on the
112
+ * store, so give network-backed primitives a timeout. Reads are not queued: they see the
113
+ * last persisted snapshot. The guarantee is scoped to one store instance in
114
+ * one JS realm — two instances over the same storage (two
115
+ * `LocalStorageStore`s on one key, two browser tabs, several server
116
+ * processes sharing a KV or a file) are not coordinated; a backend that
117
+ * needs that must bring its own atomic primitive (a transaction, a
118
+ * compare-and-set).
119
+ *
80
120
  * @example
81
121
  * ```ts
82
122
  * export class MemoryStore implements SitepingStore {
@@ -87,10 +127,14 @@ export type CollectionStore = SitepingStore & Required<Pick<SitepingStore, "veri
87
127
  * this.feedbacks = next;
88
128
  * },
89
129
  * generateId: () => crypto.randomUUID(),
130
+ * comments: true,
90
131
  * });
91
132
  * createFeedback = this.store.createFeedback;
92
133
  * // …delegate the remaining methods the same way
93
134
  * }
94
135
  * ```
95
136
  */
96
- export declare function createCollectionStore(backend: CollectionStoreBackend): CollectionStore;
137
+ export declare function createCollectionStore(backend: CollectionStoreBackend & {
138
+ comments: true;
139
+ }): CollectionStore & Required<Pick<SitepingStore, "createFeedbackIfAbsent" | "addComment" | "deleteComment">>;
140
+ export declare function createCollectionStore(backend: CollectionStoreBackend): CollectionStore & Required<Pick<SitepingStore, "createFeedbackIfAbsent">>;
@@ -7,13 +7,13 @@
7
7
  * timestamps), filter/paginate with `applyFeedbackFilters`, and implement
8
8
  * the dedup/update/delete choreography of the `SitepingStore` contract.
9
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.
10
+ * `buildFeedbackRecord` / `buildAnnotationRecord` / `buildCommentRecord`
11
+ * cover the first part for any adapter. `createCollectionStore` covers all
12
+ * of it: give it `load`, `persist`, and `generateId`, and it returns a fully
13
+ * conformant `SitepingStore` — writing a new snapshot adapter is ~20 lines
14
+ * plus its storage specifics.
15
15
  */
16
- import type { AnnotationCreateInput, AnnotationRecord, FeedbackCreateInput, FeedbackRecord, SitepingStore } from "./types.js";
16
+ import type { AnnotationCreateInput, AnnotationRecord, CommentCreateInput, CommentRecord, FeedbackCreateInput, FeedbackRecord, SitepingStore } from "./types.js";
17
17
  /**
18
18
  * Build a persisted `AnnotationRecord` from its create input — normalizes
19
19
  * the optional anchor fields to `null` and stamps identity/timestamp.
@@ -23,12 +23,20 @@ export declare function buildAnnotationRecord(input: AnnotationCreateInput, ctx:
23
23
  feedbackId: string;
24
24
  now: Date;
25
25
  }): AnnotationRecord;
26
+ /** Build a persisted `CommentRecord` from its create input — stamps identity, thread and timestamp. */
27
+ export declare function buildCommentRecord(input: CommentCreateInput, ctx: {
28
+ id: string;
29
+ feedbackId: string;
30
+ now?: Date;
31
+ }): CommentRecord;
26
32
  /**
27
33
  * Build a persisted `FeedbackRecord` (with its annotations) from a create
28
34
  * input — normalizes every optional field to `null` and stamps ids and
29
35
  * timestamps. Adapters without external screenshot storage keep the data
30
36
  * URL inline on `screenshotUrl`, which is what this helper does; adapters
31
37
  * with a `ScreenshotStorage` upload first and override `screenshotUrl`.
38
+ * It leaves `comments` out, so the record minus its `annotations` stays an
39
+ * insertable feedback row; a store with threads returns `comments: []` itself.
32
40
  */
33
41
  export declare function buildFeedbackRecord(input: FeedbackCreateInput, ctx: {
34
42
  id: string;
@@ -37,8 +45,11 @@ export declare function buildFeedbackRecord(input: FeedbackCreateInput, ctx: {
37
45
  }): FeedbackRecord;
38
46
  /**
39
47
  * 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.
48
+ * sync or async, so in-memory arrays, localStorage and async KV stores all
49
+ * fit the same three functions. When both are sync, a mutation runs from
50
+ * `load` to `persist` without yielding, so no other code in the realm can
51
+ * write in between; an async backend is only serialized against the
52
+ * engine's own queue.
42
53
  */
43
54
  export interface CollectionStoreBackend {
44
55
  /**
@@ -55,28 +66,57 @@ export interface CollectionStoreBackend {
55
66
  * update.
56
67
  */
57
68
  persist(feedbacks: FeedbackRecord[]): void | Promise<void>;
58
- /** Generate a unique id for a new feedback or annotation record. */
69
+ /** Generate a unique id for a new feedback, annotation or comment record. */
59
70
  generateId(): string;
71
+ /**
72
+ * Keep discussion threads on the records: the store gains `addComment` and
73
+ * `deleteComment`, and new records start with `comments: []`. Opt in once
74
+ * `load` hands back what `persist` wrote for them, each comment's
75
+ * `createdAt` a `Date` again (a JSON backend revives it like the record's
76
+ * own dates). Off by default, so an adapter written before threads never
77
+ * gains them — over storage never written for them — on an engine update.
78
+ */
79
+ comments?: boolean | undefined;
60
80
  }
61
81
  /**
62
82
  * A `SitepingStore` with the optional `verifyProjectOwnership` guaranteed —
63
- * what `createCollectionStore` returns.
83
+ * what `createCollectionStore` returns, which also guarantees
84
+ * `createFeedbackIfAbsent`.
64
85
  */
65
86
  export type CollectionStore = SitepingStore & Required<Pick<SitepingStore, "verifyProjectOwnership">>;
66
87
  /**
67
88
  * Build a fully conformant `SitepingStore` on top of a snapshot backend.
68
89
  *
69
90
  * The engine implements the whole store contract: clientId dedup (idempotent
70
- * create), newest-first ordering, the standard filter/pagination pipeline,
71
- * `StoreNotFoundError` on missing update/delete, project-scoped bulk delete,
72
- * and `verifyProjectOwnership`. The snapshot returned by `load` is never
73
- * mutated: every write hands `persist` a new array, so a failed write leaves
91
+ * create, with `createFeedbackIfAbsent` reporting inserts), newest-first
92
+ * ordering, the standard filter/pagination pipeline, `StoreNotFoundError` on
93
+ * missing update/delete, project-scoped bulk delete,
94
+ * `verifyProjectOwnership`, and — with `comments: true` — discussion threads
95
+ * (`addComment`, `deleteComment`) kept on each record. The snapshot returned by `load` is
96
+ * never mutated: every write hands `persist` a new array, so a failed write leaves
74
97
  * a cached snapshot exactly as it was. When `persist` fails during `createFeedback`
75
98
  * and the record carries an inline screenshot, the engine retries once
76
99
  * without the screenshot (by far the heaviest field) so the text feedback
77
100
  * survives a storage-quota hit; if that also fails, the error propagates —
78
101
  * returning the record would claim a success that was never persisted.
79
102
  *
103
+ * Mutations (`createFeedbackIfAbsent`, `createFeedback`, `updateFeedback`,
104
+ * `deleteFeedback`, `deleteAllFeedbacks`, `addComment`, `deleteComment`) run
105
+ * one at a time through a queue owned by the returned store, so concurrent
106
+ * calls — the widget's `Promise.all` bulk resolve/delete, a comment posted
107
+ * while its feedback is resolved — never start from the same snapshot and
108
+ * overwrite each other, and `createFeedbackIfAbsent` reports `created: true`
109
+ * exactly once per `clientId`. A failed mutation rejects with its own error
110
+ * and does not block the ones queued after it, but `load` and `persist` must
111
+ * always settle: one that never does stalls every later mutation on the
112
+ * store, so give network-backed primitives a timeout. Reads are not queued: they see the
113
+ * last persisted snapshot. The guarantee is scoped to one store instance in
114
+ * one JS realm — two instances over the same storage (two
115
+ * `LocalStorageStore`s on one key, two browser tabs, several server
116
+ * processes sharing a KV or a file) are not coordinated; a backend that
117
+ * needs that must bring its own atomic primitive (a transaction, a
118
+ * compare-and-set).
119
+ *
80
120
  * @example
81
121
  * ```ts
82
122
  * export class MemoryStore implements SitepingStore {
@@ -87,10 +127,14 @@ export type CollectionStore = SitepingStore & Required<Pick<SitepingStore, "veri
87
127
  * this.feedbacks = next;
88
128
  * },
89
129
  * generateId: () => crypto.randomUUID(),
130
+ * comments: true,
90
131
  * });
91
132
  * createFeedback = this.store.createFeedback;
92
133
  * // …delegate the remaining methods the same way
93
134
  * }
94
135
  * ```
95
136
  */
96
- export declare function createCollectionStore(backend: CollectionStoreBackend): CollectionStore;
137
+ export declare function createCollectionStore(backend: CollectionStoreBackend & {
138
+ comments: true;
139
+ }): CollectionStore & Required<Pick<SitepingStore, "createFeedbackIfAbsent" | "addComment" | "deleteComment">>;
140
+ export declare function createCollectionStore(backend: CollectionStoreBackend): CollectionStore & Required<Pick<SitepingStore, "createFeedbackIfAbsent">>;
@@ -41,6 +41,14 @@ export type AssertEqual<Actual, Expected> = IfEquals<Actual, Expected, true, nev
41
41
  export type Serialized<T> = {
42
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];
43
43
  };
44
+ /**
45
+ * Read-only all the way down, for plain data that is deeply frozen at
46
+ * runtime: nested objects get read-only properties, arrays become
47
+ * `readonly` arrays.
48
+ */
49
+ export type DeepReadonly<T> = T extends readonly (infer U)[] ? readonly DeepReadonly<U>[] : T extends object ? {
50
+ readonly [K in keyof T]: DeepReadonly<T[K]>;
51
+ } : T;
44
52
  /**
45
53
  * Type guard that narrows `value` to a non-null `Record<PropertyKey, unknown>`.
46
54
  * Useful when validating arbitrary inputs before reading fields.
@@ -41,6 +41,14 @@ export type AssertEqual<Actual, Expected> = IfEquals<Actual, Expected, true, nev
41
41
  export type Serialized<T> = {
42
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];
43
43
  };
44
+ /**
45
+ * Read-only all the way down, for plain data that is deeply frozen at
46
+ * runtime: nested objects get read-only properties, arrays become
47
+ * `readonly` arrays.
48
+ */
49
+ export type DeepReadonly<T> = T extends readonly (infer U)[] ? readonly DeepReadonly<U>[] : T extends object ? {
50
+ readonly [K in keyof T]: DeepReadonly<T[K]>;
51
+ } : T;
44
52
  /**
45
53
  * Type guard that narrows `value` to a non-null `Record<PropertyKey, unknown>`.
46
54
  * Useful when validating arbitrary inputs before reading fields.