@openrails/contentkit-ui 0.85.1 → 0.87.0-rc.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.
Files changed (211) hide show
  1. package/README.md +28 -7
  2. package/dist/client/beacons.d.ts +13 -4
  3. package/dist/client/beacons.js +14 -7
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/client.d.ts +3 -0
  6. package/dist/client/client.js +29 -27
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/codes.d.ts +6 -1
  9. package/dist/client/codes.js +4 -1
  10. package/dist/client/codes.js.map +1 -1
  11. package/dist/client/content/types.d.ts +4 -2
  12. package/dist/client/content/types.js.map +1 -1
  13. package/dist/client/discovery.js +3 -0
  14. package/dist/client/discovery.js.map +1 -1
  15. package/dist/client/errors.d.ts +6 -0
  16. package/dist/client/errors.js +6 -2
  17. package/dist/client/errors.js.map +1 -1
  18. package/dist/client/gallery.d.ts +3 -5
  19. package/dist/client/gallery.js +16 -20
  20. package/dist/client/gallery.js.map +1 -1
  21. package/dist/client/generated/error-codes.d.ts +1 -1
  22. package/dist/client/generated/error-codes.js +9 -1
  23. package/dist/client/generated/error-codes.js.map +1 -1
  24. package/dist/client/generated/routes.d.ts +29 -3
  25. package/dist/client/generated/routes.js +44 -3
  26. package/dist/client/generated/routes.js.map +1 -1
  27. package/dist/client/generated/wire.d.ts +60 -10
  28. package/dist/client/generated/wire.js.map +1 -1
  29. package/dist/client/index.d.ts +2 -1
  30. package/dist/client/media/queue.d.ts +4 -2
  31. package/dist/client/media/queue.js +18 -12
  32. package/dist/client/media/queue.js.map +1 -1
  33. package/dist/client/media/rules.d.ts +7 -1
  34. package/dist/client/media/rules.js +27 -21
  35. package/dist/client/media/rules.js.map +1 -1
  36. package/dist/client/media/windows.js +1 -1
  37. package/dist/client/media/windows.js.map +1 -1
  38. package/dist/client/stats.d.ts +32 -0
  39. package/dist/client/stats.js +35 -0
  40. package/dist/client/stats.js.map +1 -0
  41. package/dist/client/versions.d.ts +6 -4
  42. package/dist/client/versions.js +24 -26
  43. package/dist/client/versions.js.map +1 -1
  44. package/dist/client.js +30 -29
  45. package/dist/components/admin/content-manager.js +206 -206
  46. package/dist/components/admin/content-manager.js.map +1 -1
  47. package/dist/components/admin/group-manager.js +71 -70
  48. package/dist/components/admin/group-manager.js.map +1 -1
  49. package/dist/components/admin/media-item-panel.d.ts +24 -7
  50. package/dist/components/admin/media-item-panel.js +209 -197
  51. package/dist/components/admin/media-item-panel.js.map +1 -1
  52. package/dist/components/admin/poll-manager.js +25 -25
  53. package/dist/components/admin/stats-dashboard.d.ts +22 -0
  54. package/dist/components/admin/stats-dashboard.js +257 -0
  55. package/dist/components/admin/stats-dashboard.js.map +1 -0
  56. package/dist/components/content/card.d.ts +5 -3
  57. package/dist/components/content/card.js +155 -133
  58. package/dist/components/content/card.js.map +1 -1
  59. package/dist/components/content/download-button.d.ts +26 -0
  60. package/dist/components/content/download-button.js +51 -0
  61. package/dist/components/content/download-button.js.map +1 -0
  62. package/dist/components/content/editors.js +455 -427
  63. package/dist/components/content/editors.js.map +1 -1
  64. package/dist/components/content/grid.d.ts +8 -1
  65. package/dist/components/content/grid.js +153 -104
  66. package/dist/components/content/grid.js.map +1 -1
  67. package/dist/components/content/index.d.ts +4 -2
  68. package/dist/components/content/listing.d.ts +3 -1
  69. package/dist/components/content/listing.js +34 -34
  70. package/dist/components/content/listing.js.map +1 -1
  71. package/dist/components/content/paywall.d.ts +17 -5
  72. package/dist/components/content/paywall.js +115 -100
  73. package/dist/components/content/paywall.js.map +1 -1
  74. package/dist/components/content/reader-stats.d.ts +12 -0
  75. package/dist/components/content/reader-stats.js +47 -0
  76. package/dist/components/content/reader-stats.js.map +1 -0
  77. package/dist/components/groups/banner.js +33 -33
  78. package/dist/components/groups/directory.js +11 -11
  79. package/dist/components/groups/editor.js +293 -293
  80. package/dist/components/groups/editor.js.map +1 -1
  81. package/dist/components/groups/header.js +27 -27
  82. package/dist/components/groups/header.js.map +1 -1
  83. package/dist/components/groups/memberships.js +180 -180
  84. package/dist/components/groups/parts.d.ts +0 -2
  85. package/dist/components/groups/parts.js +9 -27
  86. package/dist/components/groups/parts.js.map +1 -1
  87. package/dist/components/media-folder-editor.js +239 -189
  88. package/dist/components/media-folder-editor.js.map +1 -1
  89. package/dist/components/media-gallery.d.ts +1 -4
  90. package/dist/components/media-gallery.js +19 -34
  91. package/dist/components/media-gallery.js.map +1 -1
  92. package/dist/components/notifications.js +17 -17
  93. package/dist/components/slot-upload.d.ts +2 -2
  94. package/dist/components/slot-upload.js +84 -79
  95. package/dist/components/slot-upload.js.map +1 -1
  96. package/dist/components/social/bans.js +34 -34
  97. package/dist/components/social/comments.js +1 -1
  98. package/dist/components/social/comments.js.map +1 -1
  99. package/dist/components/social/engagement.d.ts +2 -1
  100. package/dist/components/social/engagement.js +5 -3
  101. package/dist/components/social/engagement.js.map +1 -1
  102. package/dist/components/social/moderation.js +52 -48
  103. package/dist/components/social/moderation.js.map +1 -1
  104. package/dist/components/video-player.d.ts +2 -0
  105. package/dist/components/video-player.js +318 -314
  106. package/dist/components/video-player.js.map +1 -1
  107. package/dist/i18n/context.d.ts +2 -0
  108. package/dist/i18n/context.js +23 -5
  109. package/dist/i18n/context.js.map +1 -1
  110. package/dist/index.d.ts +2 -1
  111. package/dist/index.js +13 -10
  112. package/dist/locales/admin/de.js +27 -0
  113. package/dist/locales/admin/de.js.map +1 -1
  114. package/dist/locales/admin/en.d.ts +27 -0
  115. package/dist/locales/admin/en.js +27 -0
  116. package/dist/locales/admin/en.js.map +1 -1
  117. package/dist/locales/admin/es.js +27 -0
  118. package/dist/locales/admin/es.js.map +1 -1
  119. package/dist/locales/admin/ja.js +27 -0
  120. package/dist/locales/admin/ja.js.map +1 -1
  121. package/dist/locales/admin/ko.js +27 -0
  122. package/dist/locales/admin/ko.js.map +1 -1
  123. package/dist/locales/admin/zh.js +27 -0
  124. package/dist/locales/admin/zh.js.map +1 -1
  125. package/dist/locales/content/de.js +6 -0
  126. package/dist/locales/content/de.js.map +1 -1
  127. package/dist/locales/content/en.d.ts +6 -0
  128. package/dist/locales/content/en.js +6 -0
  129. package/dist/locales/content/en.js.map +1 -1
  130. package/dist/locales/content/es.js +6 -0
  131. package/dist/locales/content/es.js.map +1 -1
  132. package/dist/locales/content/ja.js +6 -0
  133. package/dist/locales/content/ja.js.map +1 -1
  134. package/dist/locales/content/ko.js +6 -0
  135. package/dist/locales/content/ko.js.map +1 -1
  136. package/dist/locales/content/zh.js +6 -0
  137. package/dist/locales/content/zh.js.map +1 -1
  138. package/dist/locales/de.js +14 -2
  139. package/dist/locales/de.js.map +1 -1
  140. package/dist/locales/discovery/de.js +3 -0
  141. package/dist/locales/discovery/de.js.map +1 -1
  142. package/dist/locales/discovery/en.d.ts +3 -0
  143. package/dist/locales/discovery/en.js +3 -0
  144. package/dist/locales/discovery/en.js.map +1 -1
  145. package/dist/locales/discovery/es.js +3 -0
  146. package/dist/locales/discovery/es.js.map +1 -1
  147. package/dist/locales/discovery/ja.js +3 -0
  148. package/dist/locales/discovery/ja.js.map +1 -1
  149. package/dist/locales/discovery/ko.js +3 -0
  150. package/dist/locales/discovery/ko.js.map +1 -1
  151. package/dist/locales/discovery/zh.js +3 -0
  152. package/dist/locales/discovery/zh.js.map +1 -1
  153. package/dist/locales/en.d.ts +50 -2
  154. package/dist/locales/en.js +14 -2
  155. package/dist/locales/en.js.map +1 -1
  156. package/dist/locales/es.js +14 -2
  157. package/dist/locales/es.js.map +1 -1
  158. package/dist/locales/ja.js +14 -2
  159. package/dist/locales/ja.js.map +1 -1
  160. package/dist/locales/ko.js +14 -2
  161. package/dist/locales/ko.js.map +1 -1
  162. package/dist/locales/zh.js +14 -2
  163. package/dist/locales/zh.js.map +1 -1
  164. package/dist/react/admin.js +22 -23
  165. package/dist/react/admin.js.map +1 -1
  166. package/dist/react/canonical.d.ts +4 -3
  167. package/dist/react/canonical.js +34 -33
  168. package/dist/react/canonical.js.map +1 -1
  169. package/dist/react/comments.d.ts +1 -1
  170. package/dist/react/comments.js +3 -2
  171. package/dist/react/comments.js.map +1 -1
  172. package/dist/react/contents.js +144 -144
  173. package/dist/react/contents.js.map +1 -1
  174. package/dist/react/context.d.ts +1 -1
  175. package/dist/react/context.js.map +1 -1
  176. package/dist/react/detail.d.ts +12 -6
  177. package/dist/react/detail.js +31 -24
  178. package/dist/react/detail.js.map +1 -1
  179. package/dist/react/discovery.d.ts +11 -0
  180. package/dist/react/discovery.js +49 -30
  181. package/dist/react/discovery.js.map +1 -1
  182. package/dist/react/engagement.d.ts +6 -2
  183. package/dist/react/engagement.js +2 -6
  184. package/dist/react/engagement.js.map +1 -1
  185. package/dist/react/folder.d.ts +7 -2
  186. package/dist/react/folder.js +165 -147
  187. package/dist/react/folder.js.map +1 -1
  188. package/dist/react/groups.d.ts +0 -1
  189. package/dist/react/groups.js +13 -12
  190. package/dist/react/groups.js.map +1 -1
  191. package/dist/react/index.d.ts +6 -4
  192. package/dist/react/location.d.ts +11 -0
  193. package/dist/react/location.js +18 -0
  194. package/dist/react/location.js.map +1 -0
  195. package/dist/react/public.d.ts +8 -0
  196. package/dist/react/public.js +32 -27
  197. package/dist/react/public.js.map +1 -1
  198. package/dist/react/stats.d.ts +24 -0
  199. package/dist/react/stats.js +28 -0
  200. package/dist/react/stats.js.map +1 -0
  201. package/dist/react/use-resource.js +8 -7
  202. package/dist/react/use-resource.js.map +1 -1
  203. package/dist/react/versions.d.ts +25 -5
  204. package/dist/react/versions.js +61 -40
  205. package/dist/react/versions.js.map +1 -1
  206. package/dist/react.js +26 -24
  207. package/dist/styles.css +1 -1
  208. package/dist/urls/index.d.ts +35 -10
  209. package/dist/urls.js +100 -70
  210. package/dist/urls.js.map +1 -1
  211. package/package.json +1 -1
package/README.md CHANGED
@@ -243,14 +243,17 @@ shape, widths and narrowest edit.
243
243
  ### useCanonicalContent
244
244
 
245
245
  ```tsx
246
- useCanonicalContent(video.link, { title: video.title, image: posterURL, location: pathname + search, defaultLanguage: "en" });
246
+ useCanonicalContent(video.link, { title: video.title, image: posterURL, location: pathname + search });
247
247
  ```
248
248
 
249
249
  Once a content page's link arrives, replaces the address with its canonical
250
- path (code spelling, merged code, current slug) through the provider's
251
- `navigate`, else `history.replaceState`, and keeps `<link rel=canonical>`,
250
+ path (language prefix, the reader's when the address has none; code
251
+ spelling, merged code, current slug; the query, `?v=` included, kept) through
252
+ the provider's `navigate`, else `history.replaceState`, and keeps `<link rel=canonical>`,
252
253
  `og:url`, `og:title`, `og:image` and hreflang alternates (each language's
253
- own slug, plus `x-default`) in the head while mounted. Unmounting removes
254
+ own slug, plus `x-default`, the default language's) in the head while
255
+ mounted. As on the server, every path carries its language, the default
256
+ included, and a version's (`?v=`) canonical URL is its language page. Unmounting removes
254
257
  them, server-rendered ones included, so the next page sets its own. Needs the provider's
255
258
  `urls`; `/urls` also exports `hreflang()` and `canonicalURL()` (drops
256
259
  tracking parameters) for server rendering.
@@ -278,6 +281,20 @@ slot crops at its image's aspect, else its public preset's
278
281
  (`GET /media/presets`), so an item without an image yet still crops a 3:1
279
282
  cover at 3:1.
280
283
 
284
+ ### Card lists: decorations and locked cards
285
+
286
+ Every card list (`ContentGrid`, `ContentListing`, `ContentRail`,
287
+ `SearchResults`, `ContentHistory`) and `ContentCard` take `decorate(card)`:
288
+ the host's badges, drawn over the cover (place them yourself; clicks reach
289
+ the card, and click signals stay intact). Cards carry what badges need:
290
+ `access.level` and `access.tiers` (the effective rule, the same for every
291
+ reader), `version.has_audio`, `viewer.favorited` and `groups`.
292
+
293
+ `ContentGrid` and `ContentListing` take `locked`: `show` (default) marks the
294
+ cards the reader doesn't open, `hide` leaves them out, and `collapse` lists
295
+ the open ones and puts the rest behind a counted toggle that loads them on
296
+ opening (`useLockedCount` counts them).
297
+
281
298
  ### MediaFolderEditor
282
299
 
283
300
  ```tsx
@@ -322,8 +339,9 @@ the starting shape and edit.
322
339
  `<MediaGallery item={post} prefix="low-res/" renderLocked={unlock} />` reads
323
340
  through the client: the read (refreshed, reloaded after commits), HLS bases,
324
341
  playlist auth and the grant refresh. Passing `read`, `hlsBase`, `xhrSetup`
325
- and `refresh` yourself still works. A locked item's public teaser carries
326
- the locked count and `renderLocked`, so the unlock is on the first slide;
342
+ and `refresh` yourself still works. A locked read lists no files, so the
343
+ lock is uncounted; its public teaser carries `renderLocked`, so the unlock is
344
+ on the first slide;
327
345
  the stage is never taller than `maxHeight` (default `80svh`).
328
346
 
329
347
  ### VideoPosterPicker frames from another video
@@ -408,7 +426,10 @@ free-text answer and its groups; `results="always"` shows results before
408
426
  voting). Signed-out visitors comment under a name, react and vote where the
409
427
  server allows it (`content.Options.Anonymous`, read from the comment standing
410
428
  and `client.config()`); elsewhere they are asked to sign in (`onSignIn`, the
411
- provider's or their own) or, without one, told to.
429
+ provider's or their own) or, without one, told to. On locked content they
430
+ follow its kind's locked view (`kinds.Kind.Locked`): hidden comments show an
431
+ unlock note without a count, hidden reactions render nothing, a hidden
432
+ favorite count is left out, and cards drop hidden counts, never showing 0.
412
433
 
413
434
  Staff building blocks: `CommentBans` (paged) with `CommentBanDialog`, and
414
435
  `PollEditor` (deleting asks for a typed confirmation). The complete staff screens are the admin flows below.
@@ -5,6 +5,8 @@ export interface BeaconOptions {
5
5
  session?: () => string | undefined;
6
6
  /** How long events gather before they are sent, ms. Default 2000. */
7
7
  delay?: number;
8
+ /** How long beacons stay off after the site records no signals, ms, before the content model is read again. Default 60000. */
9
+ recheck?: number;
8
10
  }
9
11
  /** Where a click or a view came from: the list render it was shown in. */
10
12
  export interface Attribution {
@@ -26,18 +28,22 @@ export declare const eventId: () => string;
26
28
  export declare function readerSession(): string;
27
29
  /**
28
30
  * Batched, best-effort signals and exposures: gathered for `delay`, sent in
29
- * one request each, flushed with keepalive when the page hides. A site whose
30
- * content model says it records no signals gets none.
31
+ * one request each, flushed with keepalive when the page hides. While the
32
+ * content model says the site records no signals (or a send is refused as
33
+ * not_configured or unavailable) events are dropped; after `recheck` the
34
+ * next event reads the model again, so signals resume when the site's
35
+ * signal plane is back.
31
36
  */
32
37
  export declare class Beacons {
33
38
  private readonly client;
34
39
  private signals;
35
40
  private exposures;
36
41
  private timer;
37
- private off;
42
+ private offUntil;
38
43
  private recorded;
39
44
  private readonly session;
40
45
  private readonly delay;
46
+ private readonly recheck;
41
47
  constructor(client: Pick<ContentKitClient, "discovery" | "kinds">, o?: BeaconOptions);
42
48
  /** A click on a listed card. */
43
49
  click(content: string, a?: Attribution & {
@@ -50,7 +56,10 @@ export declare class Beacons {
50
56
  /** Sends what has gathered now. */
51
57
  flush(keepalive?: boolean): Promise<void>;
52
58
  private signal;
53
- /** Whether the site records signals, asked once at the first event; an unreadable model sends anyway (a not_configured answer still turns them off). */
59
+ /** Off until `recheck` passes; then the model is read again. */
60
+ private paused;
61
+ private pause;
62
+ /** Whether the site records signals, asked at the first event and after a pause; an unreadable model sends anyway (a refusal still pauses them). */
54
63
  private recording;
55
64
  private schedule;
56
65
  }
@@ -16,12 +16,13 @@ var o = class {
16
16
  signals = [];
17
17
  exposures = [];
18
18
  timer;
19
- off = !1;
19
+ offUntil = 0;
20
20
  recorded;
21
21
  session;
22
22
  delay;
23
+ recheck;
23
24
  constructor(e, t = {}) {
24
- this.client = e, this.session = t.session ?? a, this.delay = t.delay ?? 2e3, typeof addEventListener == "function" && (addEventListener("pagehide", () => void this.flush(!0)), addEventListener("visibilitychange", () => {
25
+ this.client = e, this.session = t.session ?? a, this.delay = t.delay ?? 2e3, this.recheck = t.recheck ?? 6e4, typeof addEventListener == "function" && (addEventListener("pagehide", () => void this.flush(!0)), addEventListener("visibilitychange", () => {
25
26
  typeof document < "u" && document.visibilityState === "hidden" && this.flush(!0);
26
27
  }));
27
28
  }
@@ -48,23 +49,29 @@ var o = class {
48
49
  }) };
49
50
  }
50
51
  exposure(e) {
51
- !this.off && e.shown.length && (this.exposures.push(e), this.schedule());
52
+ !this.paused() && e.shown.length && (this.exposures.push(e), this.schedule());
52
53
  }
53
54
  async flush(n = !1) {
54
55
  clearTimeout(this.timer), this.timer = void 0;
55
56
  let r = this.signals.splice(0), i = this.exposures.splice(0);
56
- if (this.off || !r.length && !i.length) return;
57
+ if (this.paused() || !r.length && !i.length) return;
57
58
  if (!await this.recording()) {
58
- this.off = !0;
59
+ this.pause();
59
60
  return;
60
61
  }
61
62
  let a = this.session(), o = [], { discovery: s } = this.client;
62
63
  for (let e = 0; e < r.length; e += t) o.push(s.signals(r.slice(e, e + t), a, { keepalive: n }));
63
64
  for (let e = 0; e < i.length; e += t) o.push(s.exposures(i.slice(e, e + t), a, { keepalive: n }));
64
- (await Promise.allSettled(o)).some((t) => t.status === "rejected" && e(t.reason).code === "not_configured") && (this.off = !0);
65
+ (await Promise.allSettled(o)).some((t) => t.status === "rejected" && ["not_configured", "unavailable"].includes(e(t.reason).code)) && this.pause();
65
66
  }
66
67
  signal(e) {
67
- this.off || (e.type === "view" && (this.signals = this.signals.filter((t) => t.event_id !== e.event_id)), this.signals.push(e), this.schedule());
68
+ this.paused() || (e.type === "view" && (this.signals = this.signals.filter((t) => t.event_id !== e.event_id)), this.signals.push(e), this.schedule());
69
+ }
70
+ paused() {
71
+ return this.offUntil > Date.now() || (this.offUntil = 0, !1);
72
+ }
73
+ pause() {
74
+ this.offUntil = Date.now() + this.recheck, this.recorded = void 0;
68
75
  }
69
76
  recording() {
70
77
  return this.recorded ??= this.client.kinds().then((e) => e.signals, () => !0);
@@ -1 +1 @@
1
- {"version":3,"file":"beacons.js","names":[],"sources":["../../src/client/beacons.ts"],"sourcesContent":["import type { ContentKitClient } from \"./client.js\";\nimport { toContentKitError } from \"./errors.js\";\nimport type { ExposureInput, SignalInput } from \"./generated/wire.js\";\n\nexport interface BeaconOptions {\n /** A signed-out visitor's session; default one per browser tab. */\n session?: () => string | undefined;\n /** How long events gather before they are sent, ms. Default 2000. */\n delay?: number;\n}\n\n/** Where a click or a view came from: the list render it was shown in. */\nexport interface Attribution {\n render_id?: string;\n surface?: string;\n /** 1-based position in that render. */\n position?: number;\n}\n\nexport interface ViewSession {\n /** Reports the view so far: pages read, seconds watched or percent scrolled; resume is where to pick up. */\n update(o: { progress?: number; duration_s?: number; resume?: string }): void;\n}\n\nconst MAX = 100;\nconst SESSION_KEY = \"contentkit.session\";\nexport const eventId = () => (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`);\n\nlet unstored: string | undefined;\n\n/** A signed-out reader's session: one per browser tab, kept across reloads. */\nexport function readerSession(): string {\n try {\n const kept = sessionStorage.getItem(SESSION_KEY);\n if (kept) return kept;\n const made = eventId();\n sessionStorage.setItem(SESSION_KEY, made);\n return made;\n } catch {\n return (unstored ??= eventId());\n }\n}\n\n/**\n * Batched, best-effort signals and exposures: gathered for `delay`, sent in\n * one request each, flushed with keepalive when the page hides. A site whose\n * content model says it records no signals gets none.\n */\nexport class Beacons {\n private signals: SignalInput[] = [];\n private exposures: ExposureInput[] = [];\n private timer: ReturnType<typeof setTimeout> | undefined;\n private off = false;\n private recorded: Promise<boolean> | undefined;\n private readonly session: () => string | undefined;\n private readonly delay: number;\n\n constructor(\n private readonly client: Pick<ContentKitClient, \"discovery\" | \"kinds\">,\n o: BeaconOptions = {},\n ) {\n this.session = o.session ?? readerSession;\n this.delay = o.delay ?? 2000;\n if (typeof addEventListener === \"function\") {\n addEventListener(\"pagehide\", () => void this.flush(true));\n addEventListener(\"visibilitychange\", () => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") void this.flush(true);\n });\n }\n }\n\n /** A click on a listed card. */\n click(content: string, a: Attribution & { version_id?: string } = {}): void {\n this.signal({ content_id: content, type: \"click\", event_id: eventId(), occurred_at: new Date().toISOString(), ...a });\n }\n\n /** A consumption session of a content (or one version): each update a revision of one event. */\n view(content: string, version?: string, a: Attribution = {}): ViewSession {\n const event = eventId();\n let revision = 0;\n return {\n update: (o) => this.signal({ content_id: content, version_id: version, type: \"view\", event_id: event, revision: ++revision, occurred_at: new Date().toISOString(), ...a, ...o }),\n };\n }\n\n /** A result list served, rendered or shown. */\n exposure(e: ExposureInput): void {\n if (this.off || !e.shown.length) return;\n this.exposures.push(e);\n this.schedule();\n }\n\n /** Sends what has gathered now. */\n async flush(keepalive = false): Promise<void> {\n clearTimeout(this.timer);\n this.timer = undefined;\n const signals = this.signals.splice(0);\n const exposures = this.exposures.splice(0);\n if (this.off || (!signals.length && !exposures.length)) return;\n if (!(await this.recording())) {\n this.off = true;\n return;\n }\n const session = this.session();\n const sends: Promise<void>[] = [];\n const { discovery } = this.client;\n for (let i = 0; i < signals.length; i += MAX) sends.push(discovery.signals(signals.slice(i, i + MAX), session, { keepalive }));\n for (let i = 0; i < exposures.length; i += MAX) sends.push(discovery.exposures(exposures.slice(i, i + MAX), session, { keepalive }));\n const done = await Promise.allSettled(sends);\n if (done.some((r) => r.status === \"rejected\" && toContentKitError(r.reason).code === \"not_configured\")) this.off = true;\n }\n\n private signal(s: SignalInput): void {\n if (this.off) return;\n // A view's later revision replaces its earlier one still waiting.\n if (s.type === \"view\") this.signals = this.signals.filter((x) => x.event_id !== s.event_id);\n this.signals.push(s);\n this.schedule();\n }\n\n /** Whether the site records signals, asked once at the first event; an unreadable model sends anyway (a not_configured answer still turns them off). */\n private recording(): Promise<boolean> {\n return (this.recorded ??= this.client.kinds().then(\n (m) => m.signals,\n () => true,\n ));\n }\n\n private schedule(): void {\n void this.recording();\n if (this.signals.length + this.exposures.length >= MAX) return void this.flush();\n this.timer ??= setTimeout(() => void this.flush(), this.delay);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"beacons.js","names":[],"sources":["../../src/client/beacons.ts"],"sourcesContent":["import type { ContentKitClient } from \"./client.js\";\nimport { toContentKitError } from \"./errors.js\";\nimport type { ExposureInput, SignalInput } from \"./generated/wire.js\";\n\nexport interface BeaconOptions {\n /** A signed-out visitor's session; default one per browser tab. */\n session?: () => string | undefined;\n /** How long events gather before they are sent, ms. Default 2000. */\n delay?: number;\n /** How long beacons stay off after the site records no signals, ms, before the content model is read again. Default 60000. */\n recheck?: number;\n}\n\n/** Where a click or a view came from: the list render it was shown in. */\nexport interface Attribution {\n render_id?: string;\n surface?: string;\n /** 1-based position in that render. */\n position?: number;\n}\n\nexport interface ViewSession {\n /** Reports the view so far: pages read, seconds watched or percent scrolled; resume is where to pick up. */\n update(o: { progress?: number; duration_s?: number; resume?: string }): void;\n}\n\nconst MAX = 100;\nconst SESSION_KEY = \"contentkit.session\";\nexport const eventId = () => (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`);\n\nlet unstored: string | undefined;\n\n/** A signed-out reader's session: one per browser tab, kept across reloads. */\nexport function readerSession(): string {\n try {\n const kept = sessionStorage.getItem(SESSION_KEY);\n if (kept) return kept;\n const made = eventId();\n sessionStorage.setItem(SESSION_KEY, made);\n return made;\n } catch {\n return (unstored ??= eventId());\n }\n}\n\n/**\n * Batched, best-effort signals and exposures: gathered for `delay`, sent in\n * one request each, flushed with keepalive when the page hides. While the\n * content model says the site records no signals (or a send is refused as\n * not_configured or unavailable) events are dropped; after `recheck` the\n * next event reads the model again, so signals resume when the site's\n * signal plane is back.\n */\nexport class Beacons {\n private signals: SignalInput[] = [];\n private exposures: ExposureInput[] = [];\n private timer: ReturnType<typeof setTimeout> | undefined;\n private offUntil = 0;\n private recorded: Promise<boolean> | undefined;\n private readonly session: () => string | undefined;\n private readonly delay: number;\n private readonly recheck: number;\n\n constructor(\n private readonly client: Pick<ContentKitClient, \"discovery\" | \"kinds\">,\n o: BeaconOptions = {},\n ) {\n this.session = o.session ?? readerSession;\n this.delay = o.delay ?? 2000;\n this.recheck = o.recheck ?? 60_000;\n if (typeof addEventListener === \"function\") {\n addEventListener(\"pagehide\", () => void this.flush(true));\n addEventListener(\"visibilitychange\", () => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") void this.flush(true);\n });\n }\n }\n\n /** A click on a listed card. */\n click(content: string, a: Attribution & { version_id?: string } = {}): void {\n this.signal({ content_id: content, type: \"click\", event_id: eventId(), occurred_at: new Date().toISOString(), ...a });\n }\n\n /** A consumption session of a content (or one version): each update a revision of one event. */\n view(content: string, version?: string, a: Attribution = {}): ViewSession {\n const event = eventId();\n let revision = 0;\n return {\n update: (o) => this.signal({ content_id: content, version_id: version, type: \"view\", event_id: event, revision: ++revision, occurred_at: new Date().toISOString(), ...a, ...o }),\n };\n }\n\n /** A result list served, rendered or shown. */\n exposure(e: ExposureInput): void {\n if (this.paused() || !e.shown.length) return;\n this.exposures.push(e);\n this.schedule();\n }\n\n /** Sends what has gathered now. */\n async flush(keepalive = false): Promise<void> {\n clearTimeout(this.timer);\n this.timer = undefined;\n const signals = this.signals.splice(0);\n const exposures = this.exposures.splice(0);\n if (this.paused() || (!signals.length && !exposures.length)) return;\n if (!(await this.recording())) {\n this.pause();\n return;\n }\n const session = this.session();\n const sends: Promise<void>[] = [];\n const { discovery } = this.client;\n for (let i = 0; i < signals.length; i += MAX) sends.push(discovery.signals(signals.slice(i, i + MAX), session, { keepalive }));\n for (let i = 0; i < exposures.length; i += MAX) sends.push(discovery.exposures(exposures.slice(i, i + MAX), session, { keepalive }));\n const done = await Promise.allSettled(sends);\n if (done.some((r) => r.status === \"rejected\" && [\"not_configured\", \"unavailable\"].includes(toContentKitError(r.reason).code))) this.pause();\n }\n\n private signal(s: SignalInput): void {\n if (this.paused()) return;\n // A view's later revision replaces its earlier one still waiting.\n if (s.type === \"view\") this.signals = this.signals.filter((x) => x.event_id !== s.event_id);\n this.signals.push(s);\n this.schedule();\n }\n\n /** Off until `recheck` passes; then the model is read again. */\n private paused(): boolean {\n if (this.offUntil > Date.now()) return true;\n this.offUntil = 0;\n return false;\n }\n\n private pause(): void {\n this.offUntil = Date.now() + this.recheck;\n this.recorded = undefined;\n }\n\n /** Whether the site records signals, asked at the first event and after a pause; an unreadable model sends anyway (a refusal still pauses them). */\n private recording(): Promise<boolean> {\n return (this.recorded ??= this.client.kinds().then(\n (m) => m.signals,\n () => true,\n ));\n }\n\n private schedule(): void {\n void this.recording();\n if (this.signals.length + this.exposures.length >= MAX) return void this.flush();\n this.timer ??= setTimeout(() => void this.flush(), this.delay);\n }\n}\n"],"mappings":"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"}
@@ -11,6 +11,7 @@ import { Batch, Config, ContentModel, RefBody, Result } from './generated/wire.j
11
11
  import { DiscoveryClient } from './discovery.js';
12
12
  import { GroupsClient } from './groups.js';
13
13
  import { OffersClient } from './offers.js';
14
+ import { StatsClient } from './stats.js';
14
15
  import { VersionsClient } from './versions.js';
15
16
  import { ContentKitChange, ContentKitModule, HttpOptions } from './http.js';
16
17
  import { MediaClient, ContentFolders, MediaOptions } from './media/client.js';
@@ -54,6 +55,8 @@ export interface ContentKitClient {
54
55
  readonly offers: OffersClient;
55
56
  /** Content-code lookup. */
56
57
  readonly codes: CodesClient;
58
+ /** Staff activity and rankings; the signed-in reader's own statistics. */
59
+ readonly stats: StatsClient;
57
60
  /** The signed-in person's native and imported notifications. */
58
61
  readonly notifications: NotificationsClient;
59
62
  /** What the content module allows: which interactions signed-out visitors may make. */
@@ -12,37 +12,39 @@ import { PollsClient as u } from "./content/polls.js";
12
12
  import { ReactionsClient as d } from "./content/reactions.js";
13
13
  import { GroupsClient as f } from "./groups.js";
14
14
  import { OffersClient as p } from "./offers.js";
15
- import { VersionsClient as m } from "./versions.js";
16
- import { Http as h } from "./http.js";
17
- import { NotificationsClient as g } from "./notifications.js";
15
+ import { StatsClient as m } from "./stats.js";
16
+ import { VersionsClient as h } from "./versions.js";
17
+ import { Http as g } from "./http.js";
18
+ import { NotificationsClient as _ } from "./notifications.js";
18
19
  //#region src/client/client.ts
19
- function _(_) {
20
- let { media: v, folders: y, ...b } = _, x = new h(b), S = new t(x, v, y);
20
+ function v(v) {
21
+ let { media: y, folders: b, ...x } = v, S = new g(x), C = new t(S, y, b);
21
22
  return {
22
- media: S,
23
- contents: new i(x),
24
- catalog: new n(x),
25
- comments: new o(x),
26
- reactions: new d(x),
27
- favorites: new c(x),
28
- polls: new u(x, S),
29
- bans: new a(x),
30
- moderation: new l(x),
31
- groups: new f(x, S),
32
- discovery: new s(x),
33
- versions: new m(x),
34
- offers: new p(x),
35
- codes: new r(x),
36
- notifications: new g(x),
37
- config: (t) => e(x, "GET", "/config", { signal: t }),
38
- kinds: (t) => e(x, "GET", "/kinds", { signal: t }),
39
- import: (t) => e(x, "POST", "/import", { body: t }),
40
- url: (e, t) => x.mount(e, t),
41
- setReadScope: (e) => x.setReadScope(e),
42
- subscribe: (e) => x.subscribe(e)
23
+ media: C,
24
+ contents: new i(S),
25
+ catalog: new n(S),
26
+ comments: new o(S),
27
+ reactions: new d(S),
28
+ favorites: new c(S),
29
+ polls: new u(S, C),
30
+ bans: new a(S),
31
+ moderation: new l(S),
32
+ groups: new f(S, C),
33
+ discovery: new s(S),
34
+ versions: new h(S),
35
+ offers: new p(S),
36
+ codes: new r(S),
37
+ stats: new m(S),
38
+ notifications: new _(S),
39
+ config: (t) => e(S, "GET", "/config", { signal: t }),
40
+ kinds: (t) => e(S, "GET", "/kinds", { signal: t }),
41
+ import: (t) => e(S, "POST", "/import", { body: t }),
42
+ url: (e, t) => S.mount(e, t),
43
+ setReadScope: (e) => S.setReadScope(e),
44
+ subscribe: (e) => S.subscribe(e)
43
45
  };
44
46
  }
45
47
  //#endregion
46
- export { _ as createContentKitClient };
48
+ export { v as createContentKitClient };
47
49
 
48
50
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../src/client/client.ts"],"sourcesContent":["import { CatalogClient } from \"./catalog.js\";\nimport { CodesClient } from \"./codes.js\";\nimport { ContentsClient } from \"./contents.js\";\nimport { BansClient } from \"./content/bans.js\";\nimport { CommentsClient } from \"./content/comments.js\";\nimport { FavoritesClient } from \"./content/favorites.js\";\nimport { ModerationClient } from \"./content/moderation.js\";\nimport { PollsClient } from \"./content/polls.js\";\nimport { ReactionsClient } from \"./content/reactions.js\";\nimport type { Batch, Config, ContentModel, RefBody, Result } from \"./generated/wire.js\";\nimport { DiscoveryClient } from \"./discovery.js\";\nimport { GroupsClient } from \"./groups.js\";\nimport { OffersClient } from \"./offers.js\";\nimport { VersionsClient } from \"./versions.js\";\nimport { Http, type ContentKitChange, type ContentKitModule, type HttpOptions } from \"./http.js\";\nimport { MediaClient, type ContentFolders, type MediaOptions } from \"./media/client.js\";\nimport { call } from \"./route.js\";\nimport { NotificationsClient } from \"./notifications.js\";\n\nexport interface ContentKitClientOptions extends HttpOptions {\n /** Upload tuning (transport, part concurrency, retries). */\n media?: MediaOptions;\n /** The media kind of poll folders, when the host renamed it (content.Media). */\n folders?: ContentFolders;\n}\n\n/**\n * One ContentKit deployment as the browser sees it. Each module is a property\n * over one shared transport (mounts, auth, language, errors, change events).\n */\nexport interface ContentKitClient {\n /** Uploads, multipart, commits, reads, frames, HLS bases, hls.js `xhrSetup` and inline images. */\n readonly media: MediaClient;\n /** Content items: reads, and writes for their creators, owner-group editors and staff. */\n readonly contents: ContentsClient;\n /** The catalog's totals by kind, language and audience. */\n readonly catalog: CatalogClient;\n /** Threads, replies, edits, tombstones, reactions, standing, the latest feed, moderation. */\n readonly comments: CommentsClient;\n /** Likes and dislikes of an item. */\n readonly reactions: ReactionsClient;\n /** The signed-in caller's favorites. */\n readonly favorites: FavoritesClient;\n /** Polls: votes, free-text answers and the staff editor. */\n readonly polls: PollsClient;\n /** Comment bans, owner and global. */\n readonly bans: BansClient;\n /** The review queue of held comments and content. */\n readonly moderation: ModerationClient;\n /** Groups: listings, pages, staff writes and memberships. */\n readonly groups: GroupsClient;\n /** Lists, search, typeahead, rails, release dates, content filters, history, signals and exposures. */\n readonly discovery: DiscoveryClient;\n /** Content versions: reader plans, reader metadata, forks and downloads. */\n readonly versions: VersionsClient;\n /** Offers on contents, versions and groups; group joins; the reader's library. */\n readonly offers: OffersClient;\n /** Content-code lookup. */\n readonly codes: CodesClient;\n /** The signed-in person's native and imported notifications. */\n readonly notifications: NotificationsClient;\n\n /** What the content module allows: which interactions signed-out visitors may make. */\n config(signal?: AbortSignal): Promise<Config>;\n /** The site's content model: languages, tiers, kinds, group kinds and content filters. */\n kinds(signal?: AbortSignal): Promise<ContentModel>;\n /** Staff bulk import (root:import:write): groups, contents, memberships, offers and aliases, idempotent per source. */\n import(batch: Batch): Promise<Result>;\n /** A module's base URL as configured (no trailing slash). */\n url(module: ContentKitModule, ref?: RefBody): string;\n /** Cache context only, not authentication. The React provider keeps this in sync. */\n setReadScope(scope: string): void;\n /** Every successful mutation; a null scope means its data predates a context change. */\n subscribe(listener: (change: ContentKitChange, scope: string | null) => void): () => void;\n}\n\nexport function createContentKitClient(options: ContentKitClientOptions): ContentKitClient {\n const { media: mediaOptions, folders, ...http } = options;\n const h = new Http(http);\n const media = new MediaClient(h, mediaOptions, folders);\n return {\n media,\n contents: new ContentsClient(h),\n catalog: new CatalogClient(h),\n comments: new CommentsClient(h),\n reactions: new ReactionsClient(h),\n favorites: new FavoritesClient(h),\n polls: new PollsClient(h, media),\n bans: new BansClient(h),\n moderation: new ModerationClient(h),\n groups: new GroupsClient(h, media),\n discovery: new DiscoveryClient(h),\n versions: new VersionsClient(h),\n offers: new OffersClient(h),\n codes: new CodesClient(h),\n notifications: new NotificationsClient(h),\n config: (signal) => call(h, \"GET\", \"/config\", { signal }),\n kinds: (signal) => call(h, \"GET\", \"/kinds\", { signal }),\n import: (batch) => call(h, \"POST\", \"/import\", { body: batch }),\n url: (module, ref) => h.mount(module, ref),\n setReadScope: (scope) => h.setReadScope(scope),\n subscribe: (listener) => h.subscribe(listener),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA4EA,SAAgB,EAAuB,GAAoD;CACzF,IAAM,EAAE,OAAO,GAAc,YAAS,GAAG,MAAS,GAC5C,IAAI,IAAI,EAAK,CAAI,GACjB,IAAQ,IAAI,EAAY,GAAG,GAAc,CAAO;CACtD,OAAO;EACL;EACA,UAAU,IAAI,EAAe,CAAC;EAC9B,SAAS,IAAI,EAAc,CAAC;EAC5B,UAAU,IAAI,EAAe,CAAC;EAC9B,WAAW,IAAI,EAAgB,CAAC;EAChC,WAAW,IAAI,EAAgB,CAAC;EAChC,OAAO,IAAI,EAAY,GAAG,CAAK;EAC/B,MAAM,IAAI,EAAW,CAAC;EACtB,YAAY,IAAI,EAAiB,CAAC;EAClC,QAAQ,IAAI,EAAa,GAAG,CAAK;EACjC,WAAW,IAAI,EAAgB,CAAC;EAChC,UAAU,IAAI,EAAe,CAAC;EAC9B,QAAQ,IAAI,EAAa,CAAC;EAC1B,OAAO,IAAI,EAAY,CAAC;EACxB,eAAe,IAAI,EAAoB,CAAC;EACxC,SAAS,MAAW,EAAK,GAAG,OAAO,WAAW,EAAE,UAAO,CAAC;EACxD,QAAQ,MAAW,EAAK,GAAG,OAAO,UAAU,EAAE,UAAO,CAAC;EACtD,SAAS,MAAU,EAAK,GAAG,QAAQ,WAAW,EAAE,MAAM,EAAM,CAAC;EAC7D,MAAM,GAAQ,MAAQ,EAAE,MAAM,GAAQ,CAAG;EACzC,eAAe,MAAU,EAAE,aAAa,CAAK;EAC7C,YAAY,MAAa,EAAE,UAAU,CAAQ;CAC/C;AACF"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../src/client/client.ts"],"sourcesContent":["import { CatalogClient } from \"./catalog.js\";\nimport { CodesClient } from \"./codes.js\";\nimport { ContentsClient } from \"./contents.js\";\nimport { BansClient } from \"./content/bans.js\";\nimport { CommentsClient } from \"./content/comments.js\";\nimport { FavoritesClient } from \"./content/favorites.js\";\nimport { ModerationClient } from \"./content/moderation.js\";\nimport { PollsClient } from \"./content/polls.js\";\nimport { ReactionsClient } from \"./content/reactions.js\";\nimport type { Batch, Config, ContentModel, RefBody, Result } from \"./generated/wire.js\";\nimport { DiscoveryClient } from \"./discovery.js\";\nimport { GroupsClient } from \"./groups.js\";\nimport { OffersClient } from \"./offers.js\";\nimport { StatsClient } from \"./stats.js\";\nimport { VersionsClient } from \"./versions.js\";\nimport { Http, type ContentKitChange, type ContentKitModule, type HttpOptions } from \"./http.js\";\nimport { MediaClient, type ContentFolders, type MediaOptions } from \"./media/client.js\";\nimport { call } from \"./route.js\";\nimport { NotificationsClient } from \"./notifications.js\";\n\nexport interface ContentKitClientOptions extends HttpOptions {\n /** Upload tuning (transport, part concurrency, retries). */\n media?: MediaOptions;\n /** The media kind of poll folders, when the host renamed it (content.Media). */\n folders?: ContentFolders;\n}\n\n/**\n * One ContentKit deployment as the browser sees it. Each module is a property\n * over one shared transport (mounts, auth, language, errors, change events).\n */\nexport interface ContentKitClient {\n /** Uploads, multipart, commits, reads, frames, HLS bases, hls.js `xhrSetup` and inline images. */\n readonly media: MediaClient;\n /** Content items: reads, and writes for their creators, owner-group editors and staff. */\n readonly contents: ContentsClient;\n /** The catalog's totals by kind, language and audience. */\n readonly catalog: CatalogClient;\n /** Threads, replies, edits, tombstones, reactions, standing, the latest feed, moderation. */\n readonly comments: CommentsClient;\n /** Likes and dislikes of an item. */\n readonly reactions: ReactionsClient;\n /** The signed-in caller's favorites. */\n readonly favorites: FavoritesClient;\n /** Polls: votes, free-text answers and the staff editor. */\n readonly polls: PollsClient;\n /** Comment bans, owner and global. */\n readonly bans: BansClient;\n /** The review queue of held comments and content. */\n readonly moderation: ModerationClient;\n /** Groups: listings, pages, staff writes and memberships. */\n readonly groups: GroupsClient;\n /** Lists, search, typeahead, rails, release dates, content filters, history, signals and exposures. */\n readonly discovery: DiscoveryClient;\n /** Content versions: reader plans, reader metadata, forks and downloads. */\n readonly versions: VersionsClient;\n /** Offers on contents, versions and groups; group joins; the reader's library. */\n readonly offers: OffersClient;\n /** Content-code lookup. */\n readonly codes: CodesClient;\n /** Staff activity and rankings; the signed-in reader's own statistics. */\n readonly stats: StatsClient;\n /** The signed-in person's native and imported notifications. */\n readonly notifications: NotificationsClient;\n\n /** What the content module allows: which interactions signed-out visitors may make. */\n config(signal?: AbortSignal): Promise<Config>;\n /** The site's content model: languages, tiers, kinds, group kinds and content filters. */\n kinds(signal?: AbortSignal): Promise<ContentModel>;\n /** Staff bulk import (root:import:write): groups, contents, memberships, offers and aliases, idempotent per source. */\n import(batch: Batch): Promise<Result>;\n /** A module's base URL as configured (no trailing slash). */\n url(module: ContentKitModule, ref?: RefBody): string;\n /** Cache context only, not authentication. The React provider keeps this in sync. */\n setReadScope(scope: string): void;\n /** Every successful mutation; a null scope means its data predates a context change. */\n subscribe(listener: (change: ContentKitChange, scope: string | null) => void): () => void;\n}\n\nexport function createContentKitClient(options: ContentKitClientOptions): ContentKitClient {\n const { media: mediaOptions, folders, ...http } = options;\n const h = new Http(http);\n const media = new MediaClient(h, mediaOptions, folders);\n return {\n media,\n contents: new ContentsClient(h),\n catalog: new CatalogClient(h),\n comments: new CommentsClient(h),\n reactions: new ReactionsClient(h),\n favorites: new FavoritesClient(h),\n polls: new PollsClient(h, media),\n bans: new BansClient(h),\n moderation: new ModerationClient(h),\n groups: new GroupsClient(h, media),\n discovery: new DiscoveryClient(h),\n versions: new VersionsClient(h),\n offers: new OffersClient(h),\n codes: new CodesClient(h),\n stats: new StatsClient(h),\n notifications: new NotificationsClient(h),\n config: (signal) => call(h, \"GET\", \"/config\", { signal }),\n kinds: (signal) => call(h, \"GET\", \"/kinds\", { signal }),\n import: (batch) => call(h, \"POST\", \"/import\", { body: batch }),\n url: (module, ref) => h.mount(module, ref),\n setReadScope: (scope) => h.setReadScope(scope),\n subscribe: (listener) => h.subscribe(listener),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+EA,SAAgB,EAAuB,GAAoD;CACzF,IAAM,EAAE,OAAO,GAAc,YAAS,GAAG,MAAS,GAC5C,IAAI,IAAI,EAAK,CAAI,GACjB,IAAQ,IAAI,EAAY,GAAG,GAAc,CAAO;CACtD,OAAO;EACL;EACA,UAAU,IAAI,EAAe,CAAC;EAC9B,SAAS,IAAI,EAAc,CAAC;EAC5B,UAAU,IAAI,EAAe,CAAC;EAC9B,WAAW,IAAI,EAAgB,CAAC;EAChC,WAAW,IAAI,EAAgB,CAAC;EAChC,OAAO,IAAI,EAAY,GAAG,CAAK;EAC/B,MAAM,IAAI,EAAW,CAAC;EACtB,YAAY,IAAI,EAAiB,CAAC;EAClC,QAAQ,IAAI,EAAa,GAAG,CAAK;EACjC,WAAW,IAAI,EAAgB,CAAC;EAChC,UAAU,IAAI,EAAe,CAAC;EAC9B,QAAQ,IAAI,EAAa,CAAC;EAC1B,OAAO,IAAI,EAAY,CAAC;EACxB,OAAO,IAAI,EAAY,CAAC;EACxB,eAAe,IAAI,EAAoB,CAAC;EACxC,SAAS,MAAW,EAAK,GAAG,OAAO,WAAW,EAAE,UAAO,CAAC;EACxD,QAAQ,MAAW,EAAK,GAAG,OAAO,UAAU,EAAE,UAAO,CAAC;EACtD,SAAS,MAAU,EAAK,GAAG,QAAQ,WAAW,EAAE,MAAM,EAAM,CAAC;EAC7D,MAAM,GAAQ,MAAQ,EAAE,MAAM,GAAQ,CAAG;EACzC,eAAe,MAAU,EAAE,aAAa,CAAK;EAC7C,YAAY,MAAa,EAAE,UAAU,CAAQ;CAC/C;AACF"}
@@ -4,9 +4,14 @@ import { Http } from './http.js';
4
4
  export declare class CodesClient {
5
5
  private readonly http;
6
6
  constructor(http: Http);
7
- /** The code's item, slugs and canonical path (lang picks the localized slug); not_found or gone. */
7
+ /**
8
+ * The code's item, slugs and canonical path (lang picks the prefix and
9
+ * localized slug; default the site's default language), and the live version
10
+ * a version code (the page's ?v=) names; not_found or gone.
11
+ */
8
12
  resolve(code: string, o?: {
9
13
  lang?: string;
14
+ version?: string;
10
15
  signal?: AbortSignal;
11
16
  }): Promise<Resolved>;
12
17
  }
@@ -8,7 +8,10 @@ var t = class {
8
8
  resolve(t, n = {}) {
9
9
  return e(this.http, "GET", "/{code}", {
10
10
  params: { code: t },
11
- query: { lang: n.lang },
11
+ query: {
12
+ lang: n.lang,
13
+ v: n.version
14
+ },
12
15
  signal: n.signal
13
16
  });
14
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"codes.js","names":[],"sources":["../../src/client/codes.ts"],"sourcesContent":["import type { Resolved } from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call } from \"./route.js\";\n\n/** Content codes: any spelling of a code resolves to its item and canonical path. */\nexport class CodesClient {\n constructor(private readonly http: Http) {}\n\n /** The code's item, slugs and canonical path (lang picks the localized slug); not_found or gone. */\n resolve(code: string, o: { lang?: string; signal?: AbortSignal } = {}): Promise<Resolved> {\n return call(this.http, \"GET\", \"/{code}\", { params: { code }, query: { lang: o.lang }, signal: o.signal });\n }\n}\n"],"mappings":";;AAKA,IAAa,IAAb,MAAyB;CACM;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,QAAQ,GAAc,IAA6C,CAAC,GAAsB;EACxF,OAAO,EAAK,KAAK,MAAM,OAAO,WAAW;GAAE,QAAQ,EAAE,QAAK;GAAG,OAAO,EAAE,MAAM,EAAE,KAAK;GAAG,QAAQ,EAAE;EAAO,CAAC;CAC1G;AACF"}
1
+ {"version":3,"file":"codes.js","names":[],"sources":["../../src/client/codes.ts"],"sourcesContent":["import type { Resolved } from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call } from \"./route.js\";\n\n/** Content codes: any spelling of a code resolves to its item and canonical path. */\nexport class CodesClient {\n constructor(private readonly http: Http) {}\n\n /**\n * The code's item, slugs and canonical path (lang picks the prefix and\n * localized slug; default the site's default language), and the live version\n * a version code (the page's ?v=) names; not_found or gone.\n */\n resolve(code: string, o: { lang?: string; version?: string; signal?: AbortSignal } = {}): Promise<Resolved> {\n return call(this.http, \"GET\", \"/{code}\", { params: { code }, query: { lang: o.lang, v: o.version }, signal: o.signal });\n }\n}\n"],"mappings":";;AAKA,IAAa,IAAb,MAAyB;CACM;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAO1C,QAAQ,GAAc,IAA+D,CAAC,GAAsB;EAC1G,OAAO,EAAK,KAAK,MAAM,OAAO,WAAW;GAAE,QAAQ,EAAE,QAAK;GAAG,OAAO;IAAE,MAAM,EAAE;IAAM,GAAG,EAAE;GAAQ;GAAG,QAAQ,EAAE;EAAO,CAAC;CACxH;AACF"}
@@ -59,11 +59,13 @@ export type ContentChange =
59
59
  type: "reaction.changed";
60
60
  target: Target;
61
61
  counts: ReactionCounts;
62
- } | {
62
+ }
63
+ /** count is absent where the target's kind's locked view hides it. */
64
+ | {
63
65
  type: "favorite.changed";
64
66
  target: Target;
65
67
  favorited: boolean;
66
- count: number;
68
+ count?: number;
67
69
  }
68
70
  /** Created, updated, voted or answered: the poll as the server now answers it. */
69
71
  | {
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../src/client/content/types.ts"],"sourcesContent":["import type { CommentBan, CommentStanding, Comment, Content, Poll, ReactionCounts, ResolveInput } from \"../generated/wire.js\";\n\n/** What an interaction targets: a content item (optionally one of its versions) or a group. */\nexport interface Target {\n type: \"contents\" | \"groups\";\n id: string;\n /** One version of a content item. */\n version?: string;\n}\n\n/** The same target. */\nexport function sameTarget(a?: Target, b?: Target): boolean {\n return !!a && !!b && a.type === b.type && a.id === b.id && (a.version ?? \"\") === (b.version ?? \"\");\n}\n\n/** A reaction: like 1, dislike -1, none 0. */\nexport type Reaction = -1 | 0 | 1;\n\n/** The route verb that sets a like or dislike; clearing is DELETE …/reaction. */\nexport function reactionVerb(value: -1 | 1): \"like\" | \"dislike\" {\n return value === 1 ? \"like\" : \"dislike\";\n}\n\n/** Comment order: newest (the default), likes (most liked) or best (Wilson lower bound). */\nexport type Sort = \"newest\" | \"likes\" | \"best\";\n\n/** A ban route family: \"owner\" bans from the caller's own content, \"global\" from the whole site. */\nexport type BanScope = CommentStanding[\"ban_scopes\"][number];\n\n/** What the review queue holds: comments and content items. */\nexport type HeldKind = \"comment\" | \"content\";\n\n/** The moderation rules: banned words or IP bans. */\nexport type RuleSet = \"words\" | \"ips\";\n\n/** A reviewer's decision on a held item. */\nexport type Decision = Exclude<ResolveInput[\"decision\"], \"review\">;\n\n/** A content-module mutation made through the client. */\nexport type ContentChange =\n /** Created or edited: the item as the server now answers it. */\n | { type: \"content.saved\"; content: Content }\n /** A version changed: refetch the item. */\n | { type: \"content.changed\"; id: string }\n | { type: \"content.deleted\"; id: string }\n /** target is the commented item. */\n | { type: \"comment.created\"; target: Target; comment: Comment }\n | { type: \"comment.updated\"; comment: Comment }\n | { type: \"comment.deleted\"; id: string }\n | { type: \"comment.restored\"; id: string }\n | { type: \"comment.reacted\"; id: string; counts: ReactionCounts }\n | { type: \"reaction.changed\"; target: Target; counts: ReactionCounts }\n | { type: \"favorite.changed\"; target: Target; favorited: boolean; count: number }\n /** Created, updated, voted or answered: the poll as the server now answers it. */\n | { type: \"poll.created\"; poll: Poll }\n | { type: \"poll.updated\"; poll: Poll }\n /** An option or image changed: refetch the poll. */\n | { type: \"poll.changed\"; id: string }\n | { type: \"poll.deleted\"; id: string }\n | { type: \"ban.saved\"; scope: BanScope; ban: CommentBan }\n | { type: \"ban.lifted\"; scope: BanScope; user: string }\n | { type: \"moderation.resolved\"; kind: HeldKind; id: string; decision: Decision }\n /** Banned words or IP bans changed. */\n | { type: \"rules.changed\"; rules: RuleSet }\n /** A version's reader metadata changed: its reader plan reads again. */\n | { type: \"reader.changed\"; content: string; version: string }\n | { type: \"groups.changed\" }\n /** The reader's content filters changed: every list reads again. */\n | { type: \"contentFilters.changed\" }\n | { type: \"history.changed\" }\n /** A search ran or recent searches were cleared. */\n | { type: \"searches.changed\" }\n /** The reader's keys changed (a join, a purchase the host reports): reads of access re-run. */\n | { type: \"access.changed\" };\n"],"mappings":";AAWA,SAAgB,EAAW,GAAY,GAAqB;CAC1D,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,SAAS,EAAE,WAAW;AACjG;AAMA,SAAgB,EAAa,GAAmC;CAC9D,OAAO,MAAU,IAAI,SAAS;AAChC"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../src/client/content/types.ts"],"sourcesContent":["import type { CommentBan, CommentStanding, Comment, Content, Poll, ReactionCounts, ResolveInput } from \"../generated/wire.js\";\n\n/** What an interaction targets: a content item (optionally one of its versions) or a group. */\nexport interface Target {\n type: \"contents\" | \"groups\";\n id: string;\n /** One version of a content item. */\n version?: string;\n}\n\n/** The same target. */\nexport function sameTarget(a?: Target, b?: Target): boolean {\n return !!a && !!b && a.type === b.type && a.id === b.id && (a.version ?? \"\") === (b.version ?? \"\");\n}\n\n/** A reaction: like 1, dislike -1, none 0. */\nexport type Reaction = -1 | 0 | 1;\n\n/** The route verb that sets a like or dislike; clearing is DELETE …/reaction. */\nexport function reactionVerb(value: -1 | 1): \"like\" | \"dislike\" {\n return value === 1 ? \"like\" : \"dislike\";\n}\n\n/** Comment order: newest (the default), likes (most liked) or best (Wilson lower bound). */\nexport type Sort = \"newest\" | \"likes\" | \"best\";\n\n/** A ban route family: \"owner\" bans from the caller's own content, \"global\" from the whole site. */\nexport type BanScope = CommentStanding[\"ban_scopes\"][number];\n\n/** What the review queue holds: comments and content items. */\nexport type HeldKind = \"comment\" | \"content\";\n\n/** The moderation rules: banned words or IP bans. */\nexport type RuleSet = \"words\" | \"ips\";\n\n/** A reviewer's decision on a held item. */\nexport type Decision = Exclude<ResolveInput[\"decision\"], \"review\">;\n\n/** A content-module mutation made through the client. */\nexport type ContentChange =\n /** Created or edited: the item as the server now answers it. */\n | { type: \"content.saved\"; content: Content }\n /** A version changed: refetch the item. */\n | { type: \"content.changed\"; id: string }\n | { type: \"content.deleted\"; id: string }\n /** target is the commented item. */\n | { type: \"comment.created\"; target: Target; comment: Comment }\n | { type: \"comment.updated\"; comment: Comment }\n | { type: \"comment.deleted\"; id: string }\n | { type: \"comment.restored\"; id: string }\n | { type: \"comment.reacted\"; id: string; counts: ReactionCounts }\n | { type: \"reaction.changed\"; target: Target; counts: ReactionCounts }\n /** count is absent where the target's kind's locked view hides it. */\n | { type: \"favorite.changed\"; target: Target; favorited: boolean; count?: number }\n /** Created, updated, voted or answered: the poll as the server now answers it. */\n | { type: \"poll.created\"; poll: Poll }\n | { type: \"poll.updated\"; poll: Poll }\n /** An option or image changed: refetch the poll. */\n | { type: \"poll.changed\"; id: string }\n | { type: \"poll.deleted\"; id: string }\n | { type: \"ban.saved\"; scope: BanScope; ban: CommentBan }\n | { type: \"ban.lifted\"; scope: BanScope; user: string }\n | { type: \"moderation.resolved\"; kind: HeldKind; id: string; decision: Decision }\n /** Banned words or IP bans changed. */\n | { type: \"rules.changed\"; rules: RuleSet }\n /** A version's reader metadata changed: its reader plan reads again. */\n | { type: \"reader.changed\"; content: string; version: string }\n | { type: \"groups.changed\" }\n /** The reader's content filters changed: every list reads again. */\n | { type: \"contentFilters.changed\" }\n | { type: \"history.changed\" }\n /** A search ran or recent searches were cleared. */\n | { type: \"searches.changed\" }\n /** The reader's keys changed (a join, a purchase the host reports): reads of access re-run. */\n | { type: \"access.changed\" };\n"],"mappings":";AAWA,SAAgB,EAAW,GAAY,GAAqB;CAC1D,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,SAAS,EAAE,WAAW;AACjG;AAMA,SAAgB,EAAa,GAAmC;CAC9D,OAAO,MAAU,IAAI,SAAS;AAChC"}
@@ -4,6 +4,9 @@ import { readerSession as t } from "./beacons.js";
4
4
  var n = /* @__PURE__ */ new Set([
5
5
  "groups",
6
6
  "exclude_groups",
7
+ "creators",
8
+ "access",
9
+ "tiers",
7
10
  "traits",
8
11
  "card_groups",
9
12
  "group_kinds"
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","names":[],"sources":["../../src/client/discovery.ts"],"sourcesContent":["import type {\n ContentFilters,\n ContentFiltersUpdate,\n ContentPage,\n ExposureInput,\n Exposures,\n RecentSearches,\n ReleaseDates,\n SignalInput,\n Signals,\n Typeahead,\n} from \"./generated/wire.js\";\nimport { readerSession } from \"./beacons.js\";\nimport type { Http } from \"./http.js\";\nimport { call } from \"./route.js\";\n\n/** A popularity period. */\nexport type Period = \"7d\" | \"30d\" | \"90d\" | \"365d\" | \"all\";\n/** A list's order; position orders by the first of `groups` (an ordered group). */\nexport type ListSort = \"newest\" | \"oldest\" | \"title\" | \"popular\" | `popular:${Period}` | \"top_rated\" | `top_rated:${Period}` | \"most_favorited\" | \"most_viewed\" | \"position\" | \"random\";\n/** How locked contents list: marked (lock, the default) or left out (hide). */\nexport type AccessMode = \"lock\" | \"hide\";\n\n/** Which contents a list holds. */\nexport interface ContentFilterQuery {\n kind?: string;\n /** Group ids every listed version is in. */\n groups?: readonly string[];\n /** Group ids no listed version is in. */\n exclude_groups?: readonly string[];\n /** Only contents this group owns: a channel's feed. */\n owner_group?: string;\n /** User ids: only contents one of them is credited on. */\n creators?: readonly string[];\n /** public (anyone opens it now) or the level it stays locked at. */\n access?: readonly (\"public\" | \"tier\" | \"group\" | \"ppv\" | \"group_ppv\")[];\n /** Only contents locked behind one of these tiers. */\n tiers?: readonly string[];\n /** Version traits every listed version has. */\n traits?: readonly string[];\n /** \"name:value\" of the kind's filterable attributes. */\n attribute?: readonly string[];\n /** Published on this day (YYYY-MM-DD) or month (YYYY-MM). */\n released_on?: string;\n /** IANA time zone of released_on and release dates; UTC by default. */\n tz?: string;\n /** The signed-in reader's unseen, in-progress or completed contents. */\n view_status?: \"unseen\" | \"in_progress\" | \"completed\";\n}\n\n/** How a list reads to its reader. */\nexport interface ReaderQuery {\n /** Only content with a live version in this language. */\n language?: string;\n /** Titles, names and paths in this language, and each card's preferred version's; language by default. */\n display_language?: string;\n mode?: AccessMode;\n}\n\n/** The group kinds cards name; every kind with a page by default. */\nexport interface CardQuery {\n card_groups?: readonly string[];\n}\n\nexport interface PageQuery {\n cursor?: string;\n limit?: number;\n}\n\nexport type ContentQuery = ContentFilterQuery & ReaderQuery & CardQuery & PageQuery & { sort?: ListSort };\nexport type SearchQuery = ContentFilterQuery &\n ReaderQuery &\n CardQuery &\n PageQuery & {\n q: string;\n sort?: \"relevance\" | ListSort;\n personalize?: boolean;\n /** A signed-out reader's session, which keeps its recent searches; default readerSession(). */\n session?: string;\n };\nexport interface RecentSearchesQuery {\n /** A signed-out reader's session; default readerSession(). */\n session?: string;\n /** 10 by default, at most 100. */\n limit?: number;\n}\nexport type RailQuery = ContentFilterQuery & ReaderQuery & CardQuery & { limit?: number };\nexport type ReleaseDatesQuery = ContentFilterQuery & ReaderQuery & PageQuery & { by?: \"month\" | \"day\" };\nexport type HistoryQuery = ReaderQuery & CardQuery & PageQuery & { kind?: string; status?: \"seen\" | \"in_progress\" | \"completed\" };\nexport interface TypeaheadQuery extends ReaderQuery {\n q: string;\n /** Only contents of this kind. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n group_kinds?: readonly string[];\n /** Per section: 8 by default, at most 20. */\n limit?: number;\n}\n\n// Comma-joined list parameters; `attribute` repeats instead.\nconst LISTS = new Set([\"groups\", \"exclude_groups\", \"traits\", \"card_groups\", \"group_kinds\"]);\n\n/** A query with its list parameters comma-joined, as discovery routes read them. */\nexport function query(q: object): Record<string, string | number | boolean | readonly string[] | undefined> {\n const out: Record<string, string | number | boolean | readonly string[] | undefined> = {};\n for (const [k, v] of Object.entries(q)) out[k] = LISTS.has(k) && Array.isArray(v) ? (v.length ? v.join(\",\") : undefined) : v;\n return out;\n}\n\n/**\n * Discovery: lists, search, typeahead, similar and recommended contents,\n * release dates, the reader's content filters and history, signals and exposures.\n */\nexport class DiscoveryClient {\n constructor(private readonly http: Http) {}\n\n /** Contents as cards, one per content item with the version that fits the reader. */\n list(q: ContentQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/contents\", { query: query(q) as never, signal });\n }\n /** A first page is kept among the reader's recent searches. */\n async search(q: SearchQuery, signal?: AbortSignal): Promise<ContentPage> {\n const emit = q.cursor ? null : this.http.captureChanges();\n const out: ContentPage = await call(this.http, \"GET\", \"/search\", { query: query({ session: readerSession(), ...q }) as never, signal });\n emit?.({ type: \"searches.changed\" });\n return out;\n }\n /** The reader's recent searches, newest first, since they last cleared them. */\n recentSearches(q: RecentSearchesQuery = {}, signal?: AbortSignal): Promise<RecentSearches> {\n return call(this.http, \"GET\", \"/me/searches\", { query: { session: readerSession(), ...q }, signal });\n }\n /** Clears one recent search, or all of them. */\n async clearSearches(o: { query?: string; session?: string } = {}): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/me/searches\", { query: { session: readerSession(), ...o } });\n emit({ type: \"searches.changed\" });\n }\n /** Contents and groups while the reader types. */\n typeahead(q: TypeaheadQuery, signal?: AbortSignal): Promise<Typeahead> {\n return call(this.http, \"GET\", \"/typeahead\", { query: query(q) as never, signal });\n }\n /** Contents like one, of its kind unless q.kind names another. */\n similar(id: string, q: RailQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/contents/{id}/similar\", { params: { id }, query: query(q) as never, signal });\n }\n /** For the signed-in reader. */\n recommendations(q: RailQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/recommendations\", { query: query(q) as never, signal });\n }\n /** Per month or day, newest first: how many contents the reader's listing shows released then, and how many are free. */\n releaseDates(q: ReleaseDatesQuery = {}, signal?: AbortSignal): Promise<ReleaseDates> {\n return call(this.http, \"GET\", \"/release-dates\", { query: query(q) as never, signal });\n }\n contentFilters(language?: string, signal?: AbortSignal): Promise<ContentFilters> {\n return call(this.http, \"GET\", \"/me/content-filters\", { query: { language }, signal });\n }\n /** Replaces the reader's filter settings; every read applies them. */\n async setContentFilters(update: ContentFiltersUpdate, language?: string): Promise<ContentFilters> {\n const emit = this.http.captureChanges();\n const out: ContentFilters = await call(this.http, \"PUT\", \"/me/content-filters\", { query: { language }, body: update });\n emit({ type: \"contentFilters.changed\" });\n return out;\n }\n /** What the reader engaged with, most recent first. */\n history(q: HistoryQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/me/history\", { query: query(q) as never, signal });\n }\n /** Erases the reader's history: of one content item, one kind or everything. */\n async forgetHistory(o: { kind?: string; content_id?: string } = {}): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/me/history\", { query: o });\n emit({ type: \"history.changed\" });\n }\n /** Views and clicks; signed out, of the session named. */\n signals(signals: SignalInput[], session?: string, o: { keepalive?: boolean } = {}): Promise<void> {\n const body: Signals = { session, signals };\n return call(this.http, \"POST\", \"/signals\", { body, keepalive: o.keepalive });\n }\n /** Result lists served, rendered or shown, for click attribution. */\n exposures(exposures: ExposureInput[], session?: string, o: { keepalive?: boolean } = {}): Promise<void> {\n const body: Exposures = { session, exposures };\n return call(this.http, \"POST\", \"/exposures\", { body, keepalive: o.keepalive });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"discovery.js","names":[],"sources":["../../src/client/discovery.ts"],"sourcesContent":["import type {\n ContentFilters,\n ContentFiltersUpdate,\n ContentPage,\n ExposureInput,\n Exposures,\n RecentSearches,\n ReleaseDates,\n SignalInput,\n Signals,\n Typeahead,\n} from \"./generated/wire.js\";\nimport { readerSession } from \"./beacons.js\";\nimport type { Http } from \"./http.js\";\nimport { call } from \"./route.js\";\n\n/** A popularity period. */\nexport type Period = \"7d\" | \"30d\" | \"90d\" | \"365d\" | \"all\";\n/** A list's order; position orders by the first of `groups` (an ordered group). */\nexport type ListSort = \"newest\" | \"oldest\" | \"title\" | \"popular\" | `popular:${Period}` | \"top_rated\" | `top_rated:${Period}` | \"most_favorited\" | \"most_viewed\" | \"position\" | \"random\";\n/** How locked contents list: marked (lock, the default) or left out (hide). */\nexport type AccessMode = \"lock\" | \"hide\";\n\n/** Which contents a list holds. */\nexport interface ContentFilterQuery {\n kind?: string;\n /** Group ids every listed version is in. */\n groups?: readonly string[];\n /** Group ids no listed version is in. */\n exclude_groups?: readonly string[];\n /** Only contents this group owns: a channel's feed. */\n owner_group?: string;\n /** User ids: only contents one of them is credited on. */\n creators?: readonly string[];\n /** public (anyone opens it now) or the level it stays locked at. */\n access?: readonly (\"public\" | \"tier\" | \"group\" | \"ppv\" | \"group_ppv\")[];\n /** Only contents locked behind one of these tiers. */\n tiers?: readonly string[];\n /** Version traits every listed version has. */\n traits?: readonly string[];\n /** \"name:value\" of the kind's filterable attributes. */\n attribute?: readonly string[];\n /** Published on this day (YYYY-MM-DD) or month (YYYY-MM). */\n released_on?: string;\n /** IANA time zone of released_on and release dates; UTC by default. */\n tz?: string;\n /** The signed-in reader's unseen, in-progress or completed contents. */\n view_status?: \"unseen\" | \"in_progress\" | \"completed\";\n}\n\n/** How a list reads to its reader. */\nexport interface ReaderQuery {\n /** Only content with a live version in this language. */\n language?: string;\n /** Titles, names and paths in this language, and each card's preferred version's; language by default. */\n display_language?: string;\n mode?: AccessMode;\n}\n\n/** The group kinds cards name; every kind with a page by default. */\nexport interface CardQuery {\n card_groups?: readonly string[];\n}\n\nexport interface PageQuery {\n cursor?: string;\n limit?: number;\n}\n\nexport type ContentQuery = ContentFilterQuery & ReaderQuery & CardQuery & PageQuery & { sort?: ListSort };\nexport type SearchQuery = ContentFilterQuery &\n ReaderQuery &\n CardQuery &\n PageQuery & {\n q: string;\n sort?: \"relevance\" | ListSort;\n personalize?: boolean;\n /** A signed-out reader's session, which keeps its recent searches; default readerSession(). */\n session?: string;\n };\nexport interface RecentSearchesQuery {\n /** A signed-out reader's session; default readerSession(). */\n session?: string;\n /** 10 by default, at most 100. */\n limit?: number;\n}\nexport type RailQuery = ContentFilterQuery & ReaderQuery & CardQuery & { limit?: number };\nexport type ReleaseDatesQuery = ContentFilterQuery & ReaderQuery & PageQuery & { by?: \"month\" | \"day\" };\nexport type HistoryQuery = ReaderQuery & CardQuery & PageQuery & { kind?: string; status?: \"seen\" | \"in_progress\" | \"completed\" };\nexport interface TypeaheadQuery extends ReaderQuery {\n q: string;\n /** Only contents of this kind. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n group_kinds?: readonly string[];\n /** Per section: 8 by default, at most 20. */\n limit?: number;\n}\n\n// Comma-joined list parameters; `attribute` repeats instead.\nconst LISTS = new Set([\"groups\", \"exclude_groups\", \"creators\", \"access\", \"tiers\", \"traits\", \"card_groups\", \"group_kinds\"]);\n\n/** A query with its list parameters comma-joined, as discovery routes read them. */\nexport function query(q: object): Record<string, string | number | boolean | readonly string[] | undefined> {\n const out: Record<string, string | number | boolean | readonly string[] | undefined> = {};\n for (const [k, v] of Object.entries(q)) out[k] = LISTS.has(k) && Array.isArray(v) ? (v.length ? v.join(\",\") : undefined) : v;\n return out;\n}\n\n/**\n * Discovery: lists, search, typeahead, similar and recommended contents,\n * release dates, the reader's content filters and history, signals and exposures.\n */\nexport class DiscoveryClient {\n constructor(private readonly http: Http) {}\n\n /** Contents as cards, one per content item with the version that fits the reader. */\n list(q: ContentQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/contents\", { query: query(q) as never, signal });\n }\n /** A first page is kept among the reader's recent searches. */\n async search(q: SearchQuery, signal?: AbortSignal): Promise<ContentPage> {\n const emit = q.cursor ? null : this.http.captureChanges();\n const out: ContentPage = await call(this.http, \"GET\", \"/search\", { query: query({ session: readerSession(), ...q }) as never, signal });\n emit?.({ type: \"searches.changed\" });\n return out;\n }\n /** The reader's recent searches, newest first, since they last cleared them. */\n recentSearches(q: RecentSearchesQuery = {}, signal?: AbortSignal): Promise<RecentSearches> {\n return call(this.http, \"GET\", \"/me/searches\", { query: { session: readerSession(), ...q }, signal });\n }\n /** Clears one recent search, or all of them. */\n async clearSearches(o: { query?: string; session?: string } = {}): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/me/searches\", { query: { session: readerSession(), ...o } });\n emit({ type: \"searches.changed\" });\n }\n /** Contents and groups while the reader types. */\n typeahead(q: TypeaheadQuery, signal?: AbortSignal): Promise<Typeahead> {\n return call(this.http, \"GET\", \"/typeahead\", { query: query(q) as never, signal });\n }\n /** Contents like one, of its kind unless q.kind names another. */\n similar(id: string, q: RailQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/contents/{id}/similar\", { params: { id }, query: query(q) as never, signal });\n }\n /** For the signed-in reader. */\n recommendations(q: RailQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/recommendations\", { query: query(q) as never, signal });\n }\n /** Per month or day, newest first: how many contents the reader's listing shows released then, and how many are free. */\n releaseDates(q: ReleaseDatesQuery = {}, signal?: AbortSignal): Promise<ReleaseDates> {\n return call(this.http, \"GET\", \"/release-dates\", { query: query(q) as never, signal });\n }\n contentFilters(language?: string, signal?: AbortSignal): Promise<ContentFilters> {\n return call(this.http, \"GET\", \"/me/content-filters\", { query: { language }, signal });\n }\n /** Replaces the reader's filter settings; every read applies them. */\n async setContentFilters(update: ContentFiltersUpdate, language?: string): Promise<ContentFilters> {\n const emit = this.http.captureChanges();\n const out: ContentFilters = await call(this.http, \"PUT\", \"/me/content-filters\", { query: { language }, body: update });\n emit({ type: \"contentFilters.changed\" });\n return out;\n }\n /** What the reader engaged with, most recent first. */\n history(q: HistoryQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/me/history\", { query: query(q) as never, signal });\n }\n /** Erases the reader's history: of one content item, one kind or everything. */\n async forgetHistory(o: { kind?: string; content_id?: string } = {}): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/me/history\", { query: o });\n emit({ type: \"history.changed\" });\n }\n /** Views and clicks; signed out, of the session named. */\n signals(signals: SignalInput[], session?: string, o: { keepalive?: boolean } = {}): Promise<void> {\n const body: Signals = { session, signals };\n return call(this.http, \"POST\", \"/signals\", { body, keepalive: o.keepalive });\n }\n /** Result lists served, rendered or shown, for click attribution. */\n exposures(exposures: ExposureInput[], session?: string, o: { keepalive?: boolean } = {}): Promise<void> {\n const body: Exposures = { session, exposures };\n return call(this.http, \"POST\", \"/exposures\", { body, keepalive: o.keepalive });\n }\n}\n"],"mappings":"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"}
@@ -17,6 +17,8 @@ export interface ContentKitErrorInit extends ErrorOptions {
17
17
  blobs?: string[];
18
18
  ban?: BanNotice;
19
19
  action?: string;
20
+ reason?: string;
21
+ availableAt?: string;
20
22
  }
21
23
  export declare class ContentKitError extends Error {
22
24
  readonly code: ContentKitErrorCode;
@@ -28,6 +30,10 @@ export declare class ContentKitError extends Error {
28
30
  readonly blobs?: string[];
29
31
  readonly ban?: BanNotice;
30
32
  readonly action?: string;
33
+ /** A download_unavailable's code of the host's policy ("release_window"). */
34
+ readonly reason?: string;
35
+ /** A download_unavailable's opening time (RFC 3339), when a matter of time. */
36
+ readonly availableAt?: string;
31
37
  constructor(code: ContentKitErrorCode, message: string, init?: ContentKitErrorInit);
32
38
  /**
33
39
  * The request broke a rule the message states (a 4xx or a typed refusal),
@@ -16,8 +16,10 @@ var t = [
16
16
  blobs;
17
17
  ban;
18
18
  action;
19
+ reason;
20
+ availableAt;
19
21
  constructor(e, t, n = {}) {
20
- super(t, n.cause === void 0 ? void 0 : { cause: n.cause }), this.code = e, this.status = n.status ?? 0, this.retryAfter = n.retryAfter, this.details = n.details, this.blobs = n.blobs, this.ban = n.ban, this.action = n.action;
22
+ super(t, n.cause === void 0 ? void 0 : { cause: n.cause }), this.code = e, this.status = n.status ?? 0, this.retryAfter = n.retryAfter, this.details = n.details, this.blobs = n.blobs, this.ban = n.ban, this.action = n.action, this.reason = n.reason, this.availableAt = n.availableAt;
21
23
  }
22
24
  get refusal() {
23
25
  return this.status >= 400 && this.status < 500 && this.code !== "storage" || this.code.startsWith("image_") || this.code === "decode";
@@ -68,7 +70,9 @@ async function u(e) {
68
70
  blobs: t.blobs,
69
71
  details: t.details,
70
72
  ban: t.ban,
71
- action: t.action
73
+ action: t.action,
74
+ reason: t.reason,
75
+ availableAt: t.available_at
72
76
  });
73
77
  }
74
78
  function d(e) {
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../../src/client/errors.ts"],"sourcesContent":["import { CONTENTKIT_ERROR_CODES, type ErrorCode } from \"./generated/error-codes.js\";\nimport type { BanNotice, ErrorDetails, ErrorReply, Failure } from \"./generated/wire.js\";\n\n/**\n * Codes raised in the browser: network (no response), storage (the bucket\n * refused a PUT), aborted (the caller's signal), resume_mismatch (saved state\n * is not for this file), decode (not readable as an image) and render_timeout\n * (still processing after the wait).\n */\nexport const CLIENT_ERROR_CODES = [\"network\", \"storage\", \"aborted\", \"resume_mismatch\", \"decode\", \"render_timeout\"] as const;\n\nexport type ContentKitErrorCode = ErrorCode | (typeof CLIENT_ERROR_CODES)[number];\n\n/** Every code a ContentKitError can carry: the server's registry, then the client's. */\nexport const ERROR_CODES: readonly ContentKitErrorCode[] = [...(Object.keys(CONTENTKIT_ERROR_CODES) as ErrorCode[]), ...CLIENT_ERROR_CODES];\n\nexport interface ContentKitErrorInit extends ErrorOptions {\n status?: number;\n retryAfter?: number;\n details?: ErrorDetails;\n blobs?: string[];\n ban?: BanNotice;\n action?: string;\n}\n\nexport class ContentKitError extends Error {\n override readonly name = \"ContentKitError\";\n /** HTTP status; 0 for client-side codes. */\n readonly status: number;\n readonly retryAfter?: number;\n readonly details?: ErrorDetails;\n readonly blobs?: string[];\n readonly ban?: BanNotice;\n readonly action?: string;\n\n constructor(\n readonly code: ContentKitErrorCode,\n message: string,\n init: ContentKitErrorInit = {},\n ) {\n super(message, init.cause === undefined ? undefined : { cause: init.cause });\n this.status = init.status ?? 0;\n this.retryAfter = init.retryAfter;\n this.details = init.details;\n this.blobs = init.blobs;\n this.ban = init.ban;\n this.action = init.action;\n }\n\n /**\n * The request broke a rule the message states (a 4xx or a typed refusal),\n * as opposed to a fault on the server or the network: show it as is.\n */\n get refusal(): boolean {\n return (this.status >= 400 && this.status < 500 && this.code !== \"storage\") || this.code.startsWith(\"image_\") || this.code === \"decode\";\n }\n\n /** Rate or quota refusals: stop starting new requests until the limit frees. */\n get isLimit(): boolean {\n return this.code === \"rate_limited\" || this.code === \"quota_exceeded\";\n }\n\n /** The commit would exceed the kind's file caps (too_many_files, 409): remove files first. */\n get isCeiling(): boolean {\n return this.code === \"too_many_files\";\n }\n\n /** Refusals that apply to every upload of this caller, not just this file. */\n get blocksQueue(): boolean {\n return this.isLimit || this.code === \"unauthorized\" || this.code === \"forbidden\";\n }\n\n /** Worth retrying the same request. */\n get transient(): boolean {\n return (\n this.code === \"network\" ||\n this.code === \"internal_error\" ||\n this.code === \"unavailable\" ||\n (this.code === \"storage\" && (this.status >= 500 || this.status === 403 || this.status === 408 || this.status === 429))\n );\n }\n}\n\nexport const isContentKitError = (e: unknown): e is ContentKitError => e instanceof ContentKitError;\n\n/** Anything a call can throw, as a ContentKitError (network when it is not one). */\nexport function toContentKitError(e: unknown): ContentKitError {\n if (e instanceof ContentKitError) return e;\n return new ContentKitError(\"network\", e instanceof Error ? e.message : String(e), { cause: e });\n}\n\nexport function aborted(signal?: AbortSignal): ContentKitError {\n return new ContentKitError(\"aborted\", \"request canceled\", { cause: signal?.reason });\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw aborted(signal);\n}\n\nconst byStatus: Record<number, ContentKitErrorCode> = {\n 401: \"unauthorized\",\n 403: \"forbidden\",\n 404: \"not_found\",\n 409: \"conflict\",\n 410: \"gone\",\n 413: \"too_large\",\n 415: \"type_not_allowed\",\n 429: \"rate_limited\",\n 501: \"not_configured\",\n 503: \"unavailable\",\n};\n\nconst known = new Set<string>(ERROR_CODES);\n\n/** Reads a response that is not 2xx. */\nexport async function readContentKitError(res: Response): Promise<ContentKitError> {\n let body: Partial<ErrorReply> = {};\n try {\n body = (await res.json()) as Partial<ErrorReply>;\n } catch {\n // not JSON (a proxy error page)\n }\n const header = Number(res.headers.get(\"Retry-After\"));\n const retryAfter = body.retry_after ?? (Number.isFinite(header) && header > 0 ? header : undefined);\n const code = body.code && known.has(body.code) ? (body.code as ContentKitErrorCode) : (byStatus[res.status] ?? (res.status >= 500 ? \"internal_error\" : \"invalid_request\"));\n return new ContentKitError(code, body.error ?? `HTTP ${res.status}`, {\n status: res.status,\n retryAfter,\n blobs: body.blobs,\n details: body.details,\n ban: body.ban,\n action: body.action,\n });\n}\n\n/** An upload's recorded processing failure: its typed refusal, else a processing fault. */\nexport function failureError(f: Failure): ContentKitError {\n const code = f.code && known.has(f.code) ? (f.code as ContentKitErrorCode) : \"internal_error\";\n return new ContentKitError(code, f.message, { details: f.details });\n}\n"],"mappings":";;AASA,IAAa,IAAqB;CAAC;CAAW;CAAW;CAAW;CAAmB;CAAU;AAAgB,GAKpG,IAA8C,CAAC,GAAI,OAAO,KAAK,CAAsB,GAAmB,GAAG,CAAkB,GAW7H,IAAb,cAAqC,MAAM;CAW9B;CAVX,OAAyB;CAEzB;CACA;CACA;CACA;CACA;CACA;CAEA,YACE,GACA,GACA,IAA4B,CAAC,GAC7B;EAOA,AANA,MAAM,GAAS,EAAK,UAAU,KAAA,IAAY,KAAA,IAAY,EAAE,OAAO,EAAK,MAAM,CAAC,GAJlE,KAAA,OAAA,GAKT,KAAK,SAAS,EAAK,UAAU,GAC7B,KAAK,aAAa,EAAK,YACvB,KAAK,UAAU,EAAK,SACpB,KAAK,QAAQ,EAAK,OAClB,KAAK,MAAM,EAAK,KAChB,KAAK,SAAS,EAAK;CACrB;CAMA,IAAI,UAAmB;EACrB,OAAQ,KAAK,UAAU,OAAO,KAAK,SAAS,OAAO,KAAK,SAAS,aAAc,KAAK,KAAK,WAAW,QAAQ,KAAK,KAAK,SAAS;CACjI;CAGA,IAAI,UAAmB;EACrB,OAAO,KAAK,SAAS,kBAAkB,KAAK,SAAS;CACvD;CAGA,IAAI,YAAqB;EACvB,OAAO,KAAK,SAAS;CACvB;CAGA,IAAI,cAAuB;EACzB,OAAO,KAAK,WAAW,KAAK,SAAS,kBAAkB,KAAK,SAAS;CACvE;CAGA,IAAI,YAAqB;EACvB,OACE,KAAK,SAAS,aACd,KAAK,SAAS,oBACd,KAAK,SAAS,iBACb,KAAK,SAAS,cAAc,KAAK,UAAU,OAAO,KAAK,WAAW,OAAO,KAAK,WAAW,OAAO,KAAK,WAAW;CAErH;AACF,GAEa,KAAqB,MAAqC,aAAa;AAGpF,SAAgB,EAAkB,GAA6B;CAE7D,OADI,aAAa,IAAwB,IAClC,IAAI,EAAgB,WAAW,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;AAChG;AAEA,SAAgB,EAAQ,GAAuC;CAC7D,OAAO,IAAI,EAAgB,WAAW,oBAAoB,EAAE,OAAO,GAAQ,OAAO,CAAC;AACrF;AAEA,SAAgB,EAAe,GAA4B;CACzD,IAAI,GAAQ,SAAS,MAAM,EAAQ,CAAM;AAC3C;AAEA,IAAM,IAAgD;CACpD,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP,GAEM,IAAQ,IAAI,IAAY,CAAW;AAGzC,eAAsB,EAAoB,GAAyC;CACjF,IAAI,IAA4B,CAAC;CACjC,IAAI;EACF,IAAQ,MAAM,EAAI,KAAK;CACzB,QAAQ,CAER;CACA,IAAM,IAAS,OAAO,EAAI,QAAQ,IAAI,aAAa,CAAC,GAC9C,IAAa,EAAK,gBAAgB,OAAO,SAAS,CAAM,KAAK,IAAS,IAAI,IAAS,KAAA;CAEzF,OAAO,IAAI,EADE,EAAK,QAAQ,EAAM,IAAI,EAAK,IAAI,IAAK,EAAK,OAAgC,EAAS,EAAI,YAAY,EAAI,UAAU,MAAM,mBAAmB,oBACtH,EAAK,SAAS,QAAQ,EAAI,UAAU;EACnE,QAAQ,EAAI;EACZ;EACA,OAAO,EAAK;EACZ,SAAS,EAAK;EACd,KAAK,EAAK;EACV,QAAQ,EAAK;CACf,CAAC;AACH;AAGA,SAAgB,EAAa,GAA6B;CAExD,OAAO,IAAI,EADE,EAAE,QAAQ,EAAM,IAAI,EAAE,IAAI,IAAK,EAAE,OAA+B,kBAC5C,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC;AACpE"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../../src/client/errors.ts"],"sourcesContent":["import { CONTENTKIT_ERROR_CODES, type ErrorCode } from \"./generated/error-codes.js\";\nimport type { BanNotice, ErrorDetails, ErrorReply, Failure } from \"./generated/wire.js\";\n\n/**\n * Codes raised in the browser: network (no response), storage (the bucket\n * refused a PUT), aborted (the caller's signal), resume_mismatch (saved state\n * is not for this file), decode (not readable as an image) and render_timeout\n * (still processing after the wait).\n */\nexport const CLIENT_ERROR_CODES = [\"network\", \"storage\", \"aborted\", \"resume_mismatch\", \"decode\", \"render_timeout\"] as const;\n\nexport type ContentKitErrorCode = ErrorCode | (typeof CLIENT_ERROR_CODES)[number];\n\n/** Every code a ContentKitError can carry: the server's registry, then the client's. */\nexport const ERROR_CODES: readonly ContentKitErrorCode[] = [...(Object.keys(CONTENTKIT_ERROR_CODES) as ErrorCode[]), ...CLIENT_ERROR_CODES];\n\nexport interface ContentKitErrorInit extends ErrorOptions {\n status?: number;\n retryAfter?: number;\n details?: ErrorDetails;\n blobs?: string[];\n ban?: BanNotice;\n action?: string;\n reason?: string;\n availableAt?: string;\n}\n\nexport class ContentKitError extends Error {\n override readonly name = \"ContentKitError\";\n /** HTTP status; 0 for client-side codes. */\n readonly status: number;\n readonly retryAfter?: number;\n readonly details?: ErrorDetails;\n readonly blobs?: string[];\n readonly ban?: BanNotice;\n readonly action?: string;\n /** A download_unavailable's code of the host's policy (\"release_window\"). */\n readonly reason?: string;\n /** A download_unavailable's opening time (RFC 3339), when a matter of time. */\n readonly availableAt?: string;\n\n constructor(\n readonly code: ContentKitErrorCode,\n message: string,\n init: ContentKitErrorInit = {},\n ) {\n super(message, init.cause === undefined ? undefined : { cause: init.cause });\n this.status = init.status ?? 0;\n this.retryAfter = init.retryAfter;\n this.details = init.details;\n this.blobs = init.blobs;\n this.ban = init.ban;\n this.action = init.action;\n this.reason = init.reason;\n this.availableAt = init.availableAt;\n }\n\n /**\n * The request broke a rule the message states (a 4xx or a typed refusal),\n * as opposed to a fault on the server or the network: show it as is.\n */\n get refusal(): boolean {\n return (this.status >= 400 && this.status < 500 && this.code !== \"storage\") || this.code.startsWith(\"image_\") || this.code === \"decode\";\n }\n\n /** Rate or quota refusals: stop starting new requests until the limit frees. */\n get isLimit(): boolean {\n return this.code === \"rate_limited\" || this.code === \"quota_exceeded\";\n }\n\n /** The commit would exceed the kind's file caps (too_many_files, 409): remove files first. */\n get isCeiling(): boolean {\n return this.code === \"too_many_files\";\n }\n\n /** Refusals that apply to every upload of this caller, not just this file. */\n get blocksQueue(): boolean {\n return this.isLimit || this.code === \"unauthorized\" || this.code === \"forbidden\";\n }\n\n /** Worth retrying the same request. */\n get transient(): boolean {\n return (\n this.code === \"network\" ||\n this.code === \"internal_error\" ||\n this.code === \"unavailable\" ||\n (this.code === \"storage\" && (this.status >= 500 || this.status === 403 || this.status === 408 || this.status === 429))\n );\n }\n}\n\nexport const isContentKitError = (e: unknown): e is ContentKitError => e instanceof ContentKitError;\n\n/** Anything a call can throw, as a ContentKitError (network when it is not one). */\nexport function toContentKitError(e: unknown): ContentKitError {\n if (e instanceof ContentKitError) return e;\n return new ContentKitError(\"network\", e instanceof Error ? e.message : String(e), { cause: e });\n}\n\nexport function aborted(signal?: AbortSignal): ContentKitError {\n return new ContentKitError(\"aborted\", \"request canceled\", { cause: signal?.reason });\n}\n\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) throw aborted(signal);\n}\n\nconst byStatus: Record<number, ContentKitErrorCode> = {\n 401: \"unauthorized\",\n 403: \"forbidden\",\n 404: \"not_found\",\n 409: \"conflict\",\n 410: \"gone\",\n 413: \"too_large\",\n 415: \"type_not_allowed\",\n 429: \"rate_limited\",\n 501: \"not_configured\",\n 503: \"unavailable\",\n};\n\nconst known = new Set<string>(ERROR_CODES);\n\n/** Reads a response that is not 2xx. */\nexport async function readContentKitError(res: Response): Promise<ContentKitError> {\n let body: Partial<ErrorReply> = {};\n try {\n body = (await res.json()) as Partial<ErrorReply>;\n } catch {\n // not JSON (a proxy error page)\n }\n const header = Number(res.headers.get(\"Retry-After\"));\n const retryAfter = body.retry_after ?? (Number.isFinite(header) && header > 0 ? header : undefined);\n const code = body.code && known.has(body.code) ? (body.code as ContentKitErrorCode) : (byStatus[res.status] ?? (res.status >= 500 ? \"internal_error\" : \"invalid_request\"));\n return new ContentKitError(code, body.error ?? `HTTP ${res.status}`, {\n status: res.status,\n retryAfter,\n blobs: body.blobs,\n details: body.details,\n ban: body.ban,\n action: body.action,\n reason: body.reason,\n availableAt: body.available_at,\n });\n}\n\n/** An upload's recorded processing failure: its typed refusal, else a processing fault. */\nexport function failureError(f: Failure): ContentKitError {\n const code = f.code && known.has(f.code) ? (f.code as ContentKitErrorCode) : \"internal_error\";\n return new ContentKitError(code, f.message, { details: f.details });\n}\n"],"mappings":";;AASA,IAAa,IAAqB;CAAC;CAAW;CAAW;CAAW;CAAmB;CAAU;AAAgB,GAKpG,IAA8C,CAAC,GAAI,OAAO,KAAK,CAAsB,GAAmB,GAAG,CAAkB,GAa7H,IAAb,cAAqC,MAAM;CAe9B;CAdX,OAAyB;CAEzB;CACA;CACA;CACA;CACA;CACA;CAEA;CAEA;CAEA,YACE,GACA,GACA,IAA4B,CAAC,GAC7B;EASA,AARA,MAAM,GAAS,EAAK,UAAU,KAAA,IAAY,KAAA,IAAY,EAAE,OAAO,EAAK,MAAM,CAAC,GAJlE,KAAA,OAAA,GAKT,KAAK,SAAS,EAAK,UAAU,GAC7B,KAAK,aAAa,EAAK,YACvB,KAAK,UAAU,EAAK,SACpB,KAAK,QAAQ,EAAK,OAClB,KAAK,MAAM,EAAK,KAChB,KAAK,SAAS,EAAK,QACnB,KAAK,SAAS,EAAK,QACnB,KAAK,cAAc,EAAK;CAC1B;CAMA,IAAI,UAAmB;EACrB,OAAQ,KAAK,UAAU,OAAO,KAAK,SAAS,OAAO,KAAK,SAAS,aAAc,KAAK,KAAK,WAAW,QAAQ,KAAK,KAAK,SAAS;CACjI;CAGA,IAAI,UAAmB;EACrB,OAAO,KAAK,SAAS,kBAAkB,KAAK,SAAS;CACvD;CAGA,IAAI,YAAqB;EACvB,OAAO,KAAK,SAAS;CACvB;CAGA,IAAI,cAAuB;EACzB,OAAO,KAAK,WAAW,KAAK,SAAS,kBAAkB,KAAK,SAAS;CACvE;CAGA,IAAI,YAAqB;EACvB,OACE,KAAK,SAAS,aACd,KAAK,SAAS,oBACd,KAAK,SAAS,iBACb,KAAK,SAAS,cAAc,KAAK,UAAU,OAAO,KAAK,WAAW,OAAO,KAAK,WAAW,OAAO,KAAK,WAAW;CAErH;AACF,GAEa,KAAqB,MAAqC,aAAa;AAGpF,SAAgB,EAAkB,GAA6B;CAE7D,OADI,aAAa,IAAwB,IAClC,IAAI,EAAgB,WAAW,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;AAChG;AAEA,SAAgB,EAAQ,GAAuC;CAC7D,OAAO,IAAI,EAAgB,WAAW,oBAAoB,EAAE,OAAO,GAAQ,OAAO,CAAC;AACrF;AAEA,SAAgB,EAAe,GAA4B;CACzD,IAAI,GAAQ,SAAS,MAAM,EAAQ,CAAM;AAC3C;AAEA,IAAM,IAAgD;CACpD,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACP,GAEM,IAAQ,IAAI,IAAY,CAAW;AAGzC,eAAsB,EAAoB,GAAyC;CACjF,IAAI,IAA4B,CAAC;CACjC,IAAI;EACF,IAAQ,MAAM,EAAI,KAAK;CACzB,QAAQ,CAER;CACA,IAAM,IAAS,OAAO,EAAI,QAAQ,IAAI,aAAa,CAAC,GAC9C,IAAa,EAAK,gBAAgB,OAAO,SAAS,CAAM,KAAK,IAAS,IAAI,IAAS,KAAA;CAEzF,OAAO,IAAI,EADE,EAAK,QAAQ,EAAM,IAAI,EAAK,IAAI,IAAK,EAAK,OAAgC,EAAS,EAAI,YAAY,EAAI,UAAU,MAAM,mBAAmB,oBACtH,EAAK,SAAS,QAAQ,EAAI,UAAU;EACnE,QAAQ,EAAI;EACZ;EACA,OAAO,EAAK;EACZ,SAAS,EAAK;EACd,KAAK,EAAK;EACV,QAAQ,EAAK;EACb,QAAQ,EAAK;EACb,aAAa,EAAK;CACpB,CAAC;AACH;AAGA,SAAgB,EAAa,GAA6B;CAExD,OAAO,IAAI,EADE,EAAE,QAAQ,EAAM,IAAI,EAAE,IAAI,IAAK,EAAE,OAA+B,kBAC5C,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC;AACpE"}
@@ -10,12 +10,10 @@ export interface GalleryMediaItem {
10
10
  /** Width / height from the read API; a default when unknown. */
11
11
  aspect: number;
12
12
  }
13
- /** What a viewer without access is missing; `backdrop` is the public preview drawn behind it. */
13
+ /** What a viewer without access is missing, uncounted; `backdrop` is the public preview drawn behind it. */
14
14
  export interface GalleryLockedItem {
15
15
  kind: "locked";
16
16
  key: string;
17
- count: number;
18
- videos: number;
19
17
  backdrop?: string;
20
18
  aspect: number;
21
19
  }
@@ -31,8 +29,8 @@ export declare const isSubtitleType: (type?: string) => boolean;
31
29
  * The read result as gallery items in manifest order. With access: each
32
30
  * image, each playable HLS ladder (a folder in `hls`: a video, or an
33
31
  * audio-only one) and each other audio file. Without: the item's public
34
- * previews (the read's `previews`), then one locked item for the rest; an
35
- * item's private files are all or nothing, so locked files never carry URLs.
32
+ * previews (the read's `previews`), then one locked item when the read says
33
+ * it holds locked files; a locked read lists none, so nothing counts them.
36
34
  * Other files (plain videos, subtitles, zips) are not items; scope the read
37
35
  * with a prefix to choose.
38
36
  */