@neta-art/cohub 12.1.0 → 12.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +8 -5
  2. package/dist/board/editor/editor.d.ts +1 -0
  3. package/dist/board/editor/editor.js +4 -0
  4. package/dist/board/editor/snapshots.js +5 -57
  5. package/dist/board/export/index.d.ts +2 -2
  6. package/dist/board/export/node/fonts.js +4 -0
  7. package/dist/board/export/node/index.d.ts +3 -0
  8. package/dist/board/export/node/index.js +13 -1
  9. package/dist/board/export/scene.d.ts +2 -2
  10. package/dist/board/export/scene.js +2 -2
  11. package/dist/board/model/asset-key.d.ts +16 -0
  12. package/dist/board/model/asset-key.js +92 -0
  13. package/dist/board/model/export-assets.d.ts +7 -3
  14. package/dist/board/model/export-assets.js +34 -13
  15. package/dist/board/model/index.d.ts +7 -5
  16. package/dist/board/model/index.js +9 -7
  17. package/dist/board/model/items/create.js +1 -1
  18. package/dist/board/model/media-sizes.d.ts +28 -0
  19. package/dist/board/model/media-sizes.js +64 -0
  20. package/dist/board/model/palette.d.ts +1 -0
  21. package/dist/board/model/palette.js +20 -130
  22. package/dist/board/model/text-metrics.d.ts +8 -3
  23. package/dist/board/model/text-metrics.js +13 -2
  24. package/dist/board/model/themes.d.ts +488 -0
  25. package/dist/board/model/themes.js +495 -0
  26. package/dist/board/render/card-tag.js +2 -2
  27. package/dist/board/render/index.d.ts +6 -4
  28. package/dist/board/render/index.js +5 -3
  29. package/dist/board/render/lod.d.ts +22 -0
  30. package/dist/board/render/lod.js +27 -0
  31. package/dist/board/render/media-interaction.js +1 -1
  32. package/dist/board/render/palette.js +2 -25
  33. package/dist/board/render/renderers/audio-card-renderer.js +2 -1
  34. package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -0
  35. package/dist/board/render/renderers/custom-card-renderer.js +5 -5
  36. package/dist/board/render/renderers/far-plate.d.ts +11 -1
  37. package/dist/board/render/renderers/far-plate.js +27 -8
  38. package/dist/board/render/renderers/file-card-renderer.js +3 -5
  39. package/dist/board/render/renderers/image-card-renderer.js +32 -6
  40. package/dist/board/render/renderers/model-card-renderer.js +1 -1
  41. package/dist/board/render/renderers/shape-card-renderer.js +25 -24
  42. package/dist/board/render/renderers/task-card-renderer.js +7 -8
  43. package/dist/board/render/renderers/text-card-renderer.js +86 -41
  44. package/dist/board/render/renderers/video-card-renderer.js +2 -1
  45. package/dist/board/render/style.d.ts +7 -2
  46. package/dist/board/render/style.js +5 -1
  47. package/dist/board/render/surface.d.ts +5 -0
  48. package/dist/board/render/surface.js +13 -3
  49. package/dist/board/render/text-fit.js +8 -12
  50. package/dist/board/render/text-measurement.d.ts +15 -1
  51. package/dist/board/render/text-measurement.js +113 -20
  52. package/dist/board/render/text-resolution.d.ts +1 -6
  53. package/dist/board/render/text-resolution.js +1 -10
  54. package/dist/board/render/theme.d.ts +15 -0
  55. package/dist/board/render/theme.js +15 -0
  56. package/dist/board/stage/assets.d.ts +15 -4
  57. package/dist/board/stage/assets.js +155 -89
  58. package/dist/board/stage/index.d.ts +5 -5
  59. package/dist/board/stage/index.js +4 -4
  60. package/dist/board/stage/lru.d.ts +13 -9
  61. package/dist/board/stage/lru.js +38 -20
  62. package/dist/board/stage/scene.d.ts +11 -1
  63. package/dist/board/stage/scene.js +213 -138
  64. package/dist/board/stage/stage.d.ts +12 -1
  65. package/dist/board/stage/stage.js +97 -24
  66. package/dist/board/stage/texture-pool.d.ts +8 -1
  67. package/dist/board/stage/texture-pool.js +37 -1
  68. package/dist/chunks/app-realtime.js +1 -1
  69. package/dist/chunks/{transport.js → http-error.js} +37 -1
  70. package/dist/chunks/http.js +30 -10
  71. package/dist/http.js +1 -1
  72. package/dist/index.js +139 -131
  73. package/dist/media.d.ts +3 -27
  74. package/dist/media.js +3 -82
  75. package/dist/protocol/dist/board-constants.js +3 -3
  76. package/dist/protocol/dist/board-layout.d.ts +17 -3
  77. package/dist/protocol/dist/board-layout.js +68 -11
  78. package/dist/protocol/dist/core/media.d.ts +35 -1
  79. package/dist/protocol/dist/core/media.js +123 -1
  80. package/dist/protocol/dist/index.d.ts +2 -2
  81. package/dist/websocket.js +951 -1
  82. package/package.json +3 -2
  83. package/dist/board/model/image-key.d.ts +0 -9
  84. package/dist/board/model/image-key.js +0 -33
  85. package/dist/chunks/websocket.js +0 -988
@@ -1,24 +1,26 @@
1
- import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
2
1
  import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
2
+ import { applyMatrix, invertMatrix } from "../../protocol/dist/board-layout.js";
3
3
  import { pointToWorld, screenPoint, stableCullRect, visibleWorldRect, worldPoint } from "../model/geometry.js";
4
4
  import { boardScriptScope, composeBoardScriptItems, prepareBoardScriptOutput, validateBoardScriptCommit } from "../model/script-scene.js";
5
5
  import { boardScriptTarget } from "../model/script-hit.js";
6
6
  import { createBoardAnimationSampler, resolveBoardAnimationCamera } from "../model/sampling.js";
7
7
  import { resolveSceneArrow } from "../model/arrow-geometry.js";
8
- import { buildFallbackShapeColors, pickBoardColor, resolveItemColor } from "../model/palette.js";
8
+ import { boardTextureEdge } from "../model/asset-key.js";
9
+ import { pickBoardColor, resolveItemColor } from "../model/palette.js";
9
10
  import { elementClientToLocal } from "../model/client-geometry.js";
10
11
  import { isCreationBoardTool } from "../editor/tool.js";
11
12
  import { resizeCursorForHandle } from "../editor/selection-transform.js";
12
13
  import { parseBoardCssColor } from "../render/css-color.js";
13
- import { defaultBoardPalette, itemAlpha } from "../render/palette.js";
14
+ import { itemAlpha } from "../render/palette.js";
15
+ import { onBoardTextFontsChange } from "../render/text-measurement.js";
14
16
  import { createBoardBackground, updateBoardBackground } from "../render/board-background.js";
15
17
  import { getBoardResolution } from "../render/text-resolution.js";
16
18
  import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
17
19
  import { createBoardScriptHost } from "../render/script-host.js";
18
20
  import { defaultBoardRenderStyle } from "../render/style.js";
19
- import { createBoardPlayer } from "../player/index.js";
21
+ import { boardRenderTheme } from "../render/theme.js";
20
22
  import { createBoardScriptObservers } from "../model/script-observers.js";
21
- import { boardTextureEdge } from "./assets.js";
23
+ import { createBoardPlayer } from "../player/index.js";
22
24
  import { boardCanvasSize, measureBoardCanvasBox } from "./canvas-size.js";
23
25
  import { observeDisplayDensity } from "./display-density.js";
24
26
  import { toBoardPointerEvent } from "./pointer.js";
@@ -42,18 +44,17 @@ const LIVE_CHANNELS = [
42
44
  "editing",
43
45
  "playback"
44
46
  ];
45
- function defaultTheme() {
46
- return {
47
- key: "dark",
48
- palette: defaultBoardPalette("dark"),
49
- colors: buildFallbackShapeColors("dark"),
50
- colorScheme: "dark"
51
- };
52
- }
47
+ const DEFAULT_THEME = {
48
+ key: "dark",
49
+ ...boardRenderTheme("dark")
50
+ };
53
51
  const NO_IDS = /* @__PURE__ */ new Set();
54
52
  const MARCH_STEP_MS = 160;
55
53
  const MARCH_SMOOTH_MS = 40;
56
54
  const TEXTURE_SETTLE_MS = 150;
55
+ const ADOPT_PIXELS_PER_FRAME = 4194304;
56
+ const LOAD_LEAD_MS = 300;
57
+ const CULL_PADDING = 96;
57
58
  const ROTATE_CURSOR = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36L21 8M21 3v5h-5' fill='none' stroke='%23fff' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M21 12a9 9 0 1 1-2.64-6.36L21 8M21 3v5h-5' fill='none' stroke='%231d1d1f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 12 12, crosshair`;
58
59
  const defaultStyles = /* @__PURE__ */ new Map();
59
60
  function styleOf(theme) {
@@ -66,7 +67,7 @@ function styleOf(theme) {
66
67
  }
67
68
  function mountBoardStage(host, options) {
68
69
  const { editor, assets } = options;
69
- const theme = options.theme ?? defaultTheme;
70
+ const theme = options.theme ?? (() => DEFAULT_THEME);
70
71
  const resolveRenderer = options.renderers ?? getBoardCardRenderer;
71
72
  let app = null;
72
73
  let world = null;
@@ -115,6 +116,8 @@ function mountBoardStage(host, options) {
115
116
  let resizeFrame = 0;
116
117
  let canvasBox = null;
117
118
  let cardEpoch = 0;
119
+ let fontEpoch = 0;
120
+ let shownEditingId = null;
118
121
  let surface = {
119
122
  width: 0,
120
123
  height: 0
@@ -192,6 +195,49 @@ function mountBoardStage(host, options) {
192
195
  }
193
196
  }, TEXTURE_SETTLE_MS);
194
197
  }
198
+ function cullView(camera) {
199
+ const view = visibleWorldRect(camera, surface.width, surface.height);
200
+ const pad = CULL_PADDING / camera.zoom;
201
+ return {
202
+ x: view.x - pad,
203
+ y: view.y - pad,
204
+ width: view.width + pad * 2,
205
+ height: view.height + pad * 2
206
+ };
207
+ }
208
+ const arrivals = /* @__PURE__ */ new Set();
209
+ function adoptArrivals() {
210
+ let pixels = 0;
211
+ for (const key of arrivals) {
212
+ const texture = assets.getTexture(key);
213
+ const size = texture ? texture.width * texture.height : 0;
214
+ if (pixels > 0 && pixels + size > ADOPT_PIXELS_PER_FRAME) {
215
+ scheduleTick();
216
+ return;
217
+ }
218
+ pixels += size;
219
+ arrivals.delete(key);
220
+ for (const id of scene?.holders(key) ?? []) {
221
+ staleIds.add(id);
222
+ naturalIds.add(id);
223
+ }
224
+ }
225
+ }
226
+ let lastCenter = null;
227
+ function loadFocus(center, camera) {
228
+ const time = performance.now();
229
+ const previous = lastCenter;
230
+ lastCenter = {
231
+ ...center,
232
+ time
233
+ };
234
+ const elapsed = previous ? time - previous.time : 0;
235
+ if (!previous || elapsed <= 0 || elapsed > 100) return center;
236
+ const lead = LOAD_LEAD_MS / elapsed;
237
+ const reach = Math.max(surface.width, surface.height) / 2 / camera.zoom;
238
+ const ahead = (from, to) => to + Math.max(-reach, Math.min(reach, (to - from) * lead));
239
+ return worldPoint(ahead(previous.x, center.x), ahead(previous.y, center.y));
240
+ }
195
241
  const naturalIds = /* @__PURE__ */ new Set();
196
242
  let liveVersion = -1;
197
243
  function adoptNaturalSizes() {
@@ -237,11 +283,12 @@ function mountBoardStage(host, options) {
237
283
  hoveredId: editor.hoverId,
238
284
  resizingIds: interaction.type === "resizing" ? new Set(interaction.origin.keys()) : /* @__PURE__ */ new Set(),
239
285
  liftedIds: liftedIds(),
286
+ editingId: editor.editingId,
240
287
  palette: current.palette,
241
288
  colors: current.colors,
242
289
  colorScheme: current.colorScheme,
243
290
  style: styleOf(current),
244
- themeKey: current.key,
291
+ themeKey: `${current.key}:${fontEpoch}`,
245
292
  rendererType: app?.renderer.type === RendererType.CANVAS ? "canvas" : "gpu",
246
293
  zoom: renderCamera().zoom,
247
294
  textResolution: textResolution.value,
@@ -636,6 +683,9 @@ function mountBoardStage(host, options) {
636
683
  world.position.set(camera.x, camera.y);
637
684
  world.scale.set(camera.zoom);
638
685
  const ids = visibleIds();
686
+ const center = pointToWorld(screenPoint(surface.width / 2, surface.height / 2), camera);
687
+ assets.focus(loadFocus(center, camera));
688
+ adoptArrivals();
639
689
  textResolution.update(textureScale());
640
690
  const context = renderContext(current, renderScene, frame.time);
641
691
  if (animationId && effectSampler) {
@@ -646,6 +696,10 @@ function mountBoardStage(host, options) {
646
696
  for (const item of renderScene.items) if (item.type === "effect" && item.props.kind === "trail" && renderScene.opacity(item.id) > 0) pinnedIds.add(item.id);
647
697
  for (const id of previews.keys()) pinnedIds.add(id);
648
698
  if (editor.editingId) pinnedIds.add(editor.editingId);
699
+ if (editor.editingId !== shownEditingId) {
700
+ for (const id of [shownEditingId, editor.editingId]) if (id) staleIds.add(id);
701
+ shownEditingId = editor.editingId;
702
+ }
649
703
  for (const id of frame.items.keys()) {
650
704
  pinnedIds.add(id);
651
705
  for (const child of renderScene.descendants(id)) pinnedIds.add(child);
@@ -659,12 +713,17 @@ function mountBoardStage(host, options) {
659
713
  getItem: (id) => renderScene.get(id) ?? null,
660
714
  visibleIds: ids,
661
715
  pinnedIds,
662
- globalSig: [cardEpoch, current.key].join("|"),
716
+ globalSig: [
717
+ cardEpoch,
718
+ current.key,
719
+ fontEpoch
720
+ ].join("|"),
663
721
  staleIds,
664
722
  structureVersion: editor.structureVersion,
665
- geometryVersion: editor.geometryVersion,
666
723
  gestureActive: editor.gestureActive && editor.interaction.type !== "panning",
667
- focus: pointToWorld(screenPoint(surface.width / 2, surface.height / 2), camera)
724
+ focus: center,
725
+ view: ids ? cullView(camera) : void 0,
726
+ lod: options.lod?.()
668
727
  });
669
728
  staleIds.clear();
670
729
  if (scene.liveVersion !== liveVersion) {
@@ -855,13 +914,14 @@ function mountBoardStage(host, options) {
855
914
  ["dblclick", handleDoubleClick]
856
915
  ];
857
916
  const stopEditor = editor.subscribe(LIVE_CHANNELS, scheduleTick);
917
+ const stopFonts = onBoardTextFontsChange(() => {
918
+ editor.relayoutText();
919
+ fontEpoch += 1;
920
+ scene?.redrawText();
921
+ scheduleTick();
922
+ });
858
923
  const stopAssets = assets.subscribe((key) => {
859
- const ids = scene?.holders(key);
860
- if (!ids?.size) return;
861
- for (const id of ids) {
862
- staleIds.add(id);
863
- naturalIds.add(id);
864
- }
924
+ arrivals.add(key);
865
925
  scheduleTick();
866
926
  });
867
927
  const resizeObserver = new ResizeObserver((entries) => {
@@ -1004,6 +1064,18 @@ function mountBoardStage(host, options) {
1004
1064
  scheduleTick();
1005
1065
  },
1006
1066
  isMaterialized: (id) => Boolean(scene?.getNode(id)),
1067
+ stats() {
1068
+ const { textures, cooling, bytes } = assets.stats();
1069
+ return {
1070
+ ...scene?.counts() ?? {
1071
+ live: 0,
1072
+ far: 0
1073
+ },
1074
+ textures,
1075
+ cooling,
1076
+ textureBytes: bytes
1077
+ };
1078
+ },
1007
1079
  toWorld: (clientX, clientY) => pointToWorld(localPoint(clientX, clientY), renderCamera()),
1008
1080
  destroy() {
1009
1081
  disposed = true;
@@ -1018,6 +1090,7 @@ function mountBoardStage(host, options) {
1018
1090
  stopMaintenance?.();
1019
1091
  stopDensity?.();
1020
1092
  stopEditor();
1093
+ stopFonts();
1021
1094
  stopAssets();
1022
1095
  resizeObserver.disconnect();
1023
1096
  cancelAnimationFrame(scriptTickFrame);
@@ -7,6 +7,13 @@ type BoardImageTexturePool = {
7
7
  /** A CDN variant that fails to process falls back to the media it came from. */
8
8
  declare function loadBoardImageTexture(url: string): Promise<Texture>;
9
9
  declare function unloadBoardImageTexture(url: string, texture: Texture | null): Promise<void>;
10
+ /**
11
+ * Decode an image no larger than `edge` along its long side, for sources
12
+ * without CDN variants. The texture is unshared; destroy it to release it.
13
+ */
14
+ declare function loadFittedImageTexture(url: string, edge: number): Promise<Texture>;
15
+ /** A smaller unshared copy of a decoded texture, or null when it cannot be read back. */
16
+ declare function shrinkBoardTexture(texture: Texture, edge: number): Promise<Texture | null>;
10
17
  /**
11
18
  * Share image textures by resolved URL without sharing Board ownership.
12
19
  *
@@ -21,4 +28,4 @@ declare function createBoardImageTexturePool(options?: {
21
28
  }): BoardImageTexturePool;
22
29
  declare const boardImageTexturePool: BoardImageTexturePool;
23
30
  //#endregion
24
- export { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture };
31
+ export { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, shrinkBoardTexture, unloadBoardImageTexture };
@@ -62,6 +62,42 @@ async function unloadBoardImageTexture(url, texture) {
62
62
  await Assets.unload(url).catch(() => {});
63
63
  if (!texture?.destroyed) texture?.destroy(true);
64
64
  }
65
+ function fit(width, height, edge) {
66
+ const scale = edge / Math.max(width, height);
67
+ return scale < 1 ? {
68
+ resizeWidth: Math.max(1, Math.round(width * scale)),
69
+ resizeHeight: Math.max(1, Math.round(height * scale))
70
+ } : null;
71
+ }
72
+ /**
73
+ * Decode an image no larger than `edge` along its long side, for sources
74
+ * without CDN variants. The texture is unshared; destroy it to release it.
75
+ */
76
+ async function loadFittedImageTexture(url, edge) {
77
+ const response = await fetch(url);
78
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
79
+ const image = await createImageBitmap(await response.blob());
80
+ const size = fit(image.width, image.height, edge);
81
+ if (!size) return Texture.from(image, true);
82
+ try {
83
+ return Texture.from(await createImageBitmap(image, {
84
+ ...size,
85
+ resizeQuality: "high"
86
+ }), true);
87
+ } finally {
88
+ image.close();
89
+ }
90
+ }
91
+ /** A smaller unshared copy of a decoded texture, or null when it cannot be read back. */
92
+ async function shrinkBoardTexture(texture, edge) {
93
+ const resource = texture.source.resource;
94
+ const size = fit(texture.width, texture.height, edge);
95
+ if (!resource || !size || typeof createImageBitmap !== "function") return null;
96
+ return Texture.from(await createImageBitmap(resource, {
97
+ ...size,
98
+ resizeQuality: "medium"
99
+ }), true);
100
+ }
65
101
  /**
66
102
  * Share image textures by resolved URL without sharing Board ownership.
67
103
  *
@@ -150,4 +186,4 @@ function createBoardImageTexturePool(options) {
150
186
  }
151
187
  const boardImageTexturePool = createBoardImageTexturePool();
152
188
  //#endregion
153
- export { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture };
189
+ export { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, shrinkBoardTexture, unloadBoardImageTexture };
@@ -1,5 +1,5 @@
1
1
  import { O as readRtcTransportStats, k as waitForIceGathering } from "./http.js";
2
- import { o as REALTIME_ROOM_EVENT_NAME_PATTERN, t as HttpError } from "./transport.js";
2
+ import { c as HttpError, p as REALTIME_ROOM_EVENT_NAME_PATTERN } from "./http-error.js";
3
3
  import { z } from "zod";
4
4
  //#region \0rolldown/runtime.js
5
5
  var __defProp = Object.defineProperty;
@@ -509,4 +509,40 @@ var HttpTransport = class {
509
509
  }
510
510
  };
511
511
  //#endregion
512
- export { requestSourceToHeaders as A, isRequestSourceClientId as C, normalizeRequestSource as D, mergeRequestSourceIntoMeta as E, isUuid as M, isUuidLike as N, parseRequestSourceFromHeaders as O, isUuidOrShortUuid as P, hasRequestSourceIdentity as S, isRequestSourceUuid as T, isRealtimeDomain as _, sanitizeAccessToken as a, COHUB_SOURCE_HEADER_NAMES as b, WS_BOARD_AWARENESS_CAPABILITY as c, WS_REALTIME_ROOM_HISTORY_CAPABILITY as d, WS_REALTIME_ROOM_RTC_CAPABILITY as f, getSessionTurnPatchStreamKey as g, getRealtimeSpaceRoom as h, matchesUnauthorizedErrorToken as i, resolveRequestSourceChannel as j, readRequestSourceFromEnv as k, WS_COMPACT_STREAM_CAPABILITY as l, getRealtimeBoardRoom as m, HttpTransport as n, REALTIME_ROOM_EVENT_NAME_PATTERN as o, WS_ROOM_SUBSCRIPTION_CAPABILITY as p, joinApiUrl as r, REALTIME_ROOM_MAX_PAYLOAD_BYTES as s, HttpError as t, WS_REALTIME_ROOM_CAPABILITY as u, normalizeRealtimeRooms as v, isRequestSourceEmpty as w, REQUEST_SOURCE_VIA_MAX_LENGTH as x, COHUB_SOURCE_HEADER as y };
512
+ //#region src/http-error.ts
513
+ /** Shared HTTP error code for every plan entitlement gate (402). */
514
+ const FEATURE_NOT_ENTITLED_ERROR_CODE = "feature_not_entitled";
515
+ /** Shared HTTP error code for a blocked usage gate (negative balance limit, 402). */
516
+ const BILLING_ACCESS_BLOCKED_ERROR_CODE = "billing_credit_limit_exceeded";
517
+ function isHttpErrorCode(error, code) {
518
+ return error instanceof HttpError && error.code === code;
519
+ }
520
+ function isFeatureNotEntitledError(error) {
521
+ return isHttpErrorCode(error, FEATURE_NOT_ENTITLED_ERROR_CODE);
522
+ }
523
+ function isBillingAccessBlockedError(error) {
524
+ return isHttpErrorCode(error, BILLING_ACCESS_BLOCKED_ERROR_CODE);
525
+ }
526
+ function isBillingAccessBlockedCode(code) {
527
+ return code === BILLING_ACCESS_BLOCKED_ERROR_CODE;
528
+ }
529
+ const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
530
+ function isBillingConversionIntent(value) {
531
+ if (!isRecord(value)) return false;
532
+ return (value.level === "soft" || value.level === "hard") && typeof value.title === "string" && typeof value.message === "string" && isRecord(value.primaryAction) && value.primaryAction.action === "open_billing_conversion";
533
+ }
534
+ /**
535
+ * Extracts the standard `billing` payload from a response body, an
536
+ * `HttpError`, or a bare billing payload (e.g. a realtime error event's
537
+ * `billing` field). Returns `null` unless it carries a valid conversion
538
+ * intent.
539
+ */
540
+ function extractBillingPayload(source) {
541
+ const root = source instanceof HttpError ? source.body : source;
542
+ if (!isRecord(root)) return null;
543
+ const billing = isRecord(root.billing) ? root.billing : root;
544
+ if (!isBillingConversionIntent(billing.conversion)) return null;
545
+ return billing;
546
+ }
547
+ //#endregion
548
+ export { isRequestSourceClientId as A, isUuidLike as B, getSessionTurnPatchStreamKey as C, COHUB_SOURCE_HEADER_NAMES as D, COHUB_SOURCE_HEADER as E, parseRequestSourceFromHeaders as F, readRequestSourceFromEnv as I, requestSourceToHeaders as L, isRequestSourceUuid as M, mergeRequestSourceIntoMeta as N, REQUEST_SOURCE_VIA_MAX_LENGTH as O, normalizeRequestSource as P, resolveRequestSourceChannel as R, getRealtimeSpaceRoom as S, normalizeRealtimeRooms as T, isUuidOrShortUuid as V, WS_REALTIME_ROOM_CAPABILITY as _, isBillingAccessBlockedError as a, WS_ROOM_SUBSCRIPTION_CAPABILITY as b, HttpError as c, matchesUnauthorizedErrorToken as d, sanitizeAccessToken as f, WS_COMPACT_STREAM_CAPABILITY as g, WS_BOARD_AWARENESS_CAPABILITY as h, isBillingAccessBlockedCode as i, isRequestSourceEmpty as j, hasRequestSourceIdentity as k, HttpTransport as l, REALTIME_ROOM_MAX_PAYLOAD_BYTES as m, FEATURE_NOT_ENTITLED_ERROR_CODE as n, isFeatureNotEntitledError as o, REALTIME_ROOM_EVENT_NAME_PATTERN as p, extractBillingPayload as r, isHttpErrorCode as s, BILLING_ACCESS_BLOCKED_ERROR_CODE as t, joinApiUrl as u, WS_REALTIME_ROOM_HISTORY_CAPABILITY as v, isRealtimeDomain as w, getRealtimeBoardRoom as x, WS_REALTIME_ROOM_RTC_CAPABILITY as y, isUuid as z };
@@ -1,4 +1,4 @@
1
- import { M as isUuid, h as getRealtimeSpaceRoom, m as getRealtimeBoardRoom, n as HttpTransport, t as HttpError } from "./transport.js";
1
+ import { S as getRealtimeSpaceRoom, c as HttpError, l as HttpTransport, s as isHttpErrorCode, x as getRealtimeBoardRoom, z as isUuid } from "./http-error.js";
2
2
  import { l as resolveExecutionToken, o as resolveApiBaseUrl } from "./environment.js";
3
3
  import { z } from "zod";
4
4
  //#region ../protocol/dist/model/metrics.js
@@ -1039,28 +1039,48 @@ var SessionAccessApi = class {
1039
1039
  };
1040
1040
  //#endregion
1041
1041
  //#region src/apis/file-list.ts
1042
+ const DIRECTORY_CHANGED_RETRY_DELAYS_MS = [
1043
+ 100,
1044
+ 250,
1045
+ 500
1046
+ ];
1042
1047
  async function listDirectory(transport, endpoint, path, options) {
1043
1048
  const pageOptions = typeof options === "object" ? options : void 0;
1044
1049
  const fetch = typeof options === "function" ? options : pageOptions?.fetch;
1045
- const entries = /* @__PURE__ */ new Map();
1046
- const seen = /* @__PURE__ */ new Set();
1047
- let cursor = pageOptions?.cursor;
1048
- let responsePath = path;
1049
- do {
1050
+ const readPage = (cursor) => {
1050
1051
  const params = new URLSearchParams({ pagination: "true" });
1051
1052
  if (path) params.set("path", path);
1052
1053
  if (pageOptions?.limit !== void 0) params.set("limit", String(pageOptions.limit));
1053
1054
  if (cursor) params.set("cursor", cursor);
1054
- const page = await transport.request(`${endpoint}?${params}`, {
1055
+ return transport.request(`${endpoint}?${params}`, {
1055
1056
  fetch,
1056
1057
  signal: pageOptions?.signal
1057
1058
  });
1058
- const nextCursor = page.nextCursor ?? null;
1059
- if (pageOptions) return {
1059
+ };
1060
+ if (pageOptions) {
1061
+ const page = await readPage(pageOptions.cursor);
1062
+ return {
1060
1063
  ...page,
1061
- nextCursor,
1064
+ nextCursor: page.nextCursor ?? null,
1062
1065
  truncated: page.truncated ?? true
1063
1066
  };
1067
+ }
1068
+ for (let attempt = 0;; attempt += 1) try {
1069
+ return await readAllPages(readPage, path);
1070
+ } catch (error) {
1071
+ const delay = DIRECTORY_CHANGED_RETRY_DELAYS_MS[attempt];
1072
+ if (delay === void 0 || !isHttpErrorCode(error, "directory_changed")) throw error;
1073
+ await new Promise((resolve) => setTimeout(resolve, delay));
1074
+ }
1075
+ }
1076
+ async function readAllPages(readPage, path) {
1077
+ const entries = /* @__PURE__ */ new Map();
1078
+ const seen = /* @__PURE__ */ new Set();
1079
+ let cursor;
1080
+ let responsePath = path;
1081
+ do {
1082
+ const page = await readPage(cursor);
1083
+ const nextCursor = page.nextCursor ?? null;
1064
1084
  responsePath = page.path;
1065
1085
  for (const entry of page.entries) entries.set(entry.path, entry);
1066
1086
  if (nextCursor && seen.has(nextCursor)) throw new Error("Server returned a repeated directory cursor.");
package/dist/http.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { n as createHttpClient, t as CohubHttpClient } from "./chunks/http.js";
2
- import { n as HttpTransport, t as HttpError } from "./chunks/transport.js";
2
+ import { c as HttpError, l as HttpTransport } from "./chunks/http-error.js";
3
3
  import { g as scopeListHasPermission, h as isUserLevelPermission, m as USER_LEVEL_PERMISSIONS, p as PERMISSIONS } from "./chunks/environment.js";
4
4
  export { CohubHttpClient, HttpError, HttpTransport, PERMISSIONS, USER_LEVEL_PERMISSIONS, createHttpClient, isUserLevelPermission, scopeListHasPermission };