@aiquants/daily-report 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,7 +25,7 @@ External projects:
25
25
  pnpm add @aiquants/daily-report react react-dom react-router drizzle-orm zod
26
26
  ```
27
27
 
28
- `react` / `react-dom` / `react-router` / `drizzle-orm` / `zod` are **peer dependencies**. Build / test: `pnpm run build` (tsup → dist) / `pnpm run test` (vitest).
28
+ `react` / `react-dom` / `react-router` / `drizzle-orm` / `zod` are **peer dependencies**. `@aiquants/sse` (the SSE wire contract, server response helpers and the reopening client) is a regular **dependency**: it arrives transitively, so consumers do not declare it. Build / test: `pnpm run build` (tsup → dist) / `pnpm run test` (vitest).
29
29
 
30
30
  ### Tailwind CSS
31
31
 
@@ -156,7 +156,7 @@ createDailyReportServer({
156
156
  - **How it reaches the service**: the resolved set is wrapped in a `DailyReportViewerScope` and passed as the **first** argument of every gated service method.
157
157
  The class is nominal (it holds a private field), so `{ visible: null }`, `{ ...scope, visible: null }` and `new DailyReportViewerScope(...)` are all type errors — the only ways to obtain one are `DailyReportViewerScope.restrictTo(tokens)` — which accepts `readonly string[]` only, so it cannot be handed a nullish value and quietly widen — and `DailyReportViewerScope.unrestricted(auditReason)`.
158
158
  Because a bare `null` is not spellable as a visibility value, `grep -rn "DailyReportViewerScope.unrestricted("` enumerates every unfiltered read in a codebase, each carrying a written reason.
159
- - **Performance**: the port is called once per request on the critical path (cache keys incorporate the resolved set), so keep it fast — cache grants per session/user with a short TTL (≤ 60s). A live SSE connection re-resolves on its existing keep-alive tick (~60s) to bound how long a revoked grant keeps streaming; that call passes `reason: "refresh"` and reuses the connection's original `Request`, so a cache keyed on request identity **must** be bypassed when `reason === "refresh"` or the re-check silently returns the stale set.
159
+ - **Performance**: the port is called once per request on the critical path (cache keys incorporate the resolved set), so keep it fast — cache grants per session/user with a short TTL (≤ 60s). A live SSE connection re-resolves on a dedicated 60 s timer (independent of the heartbeat) to bound how long a revoked grant keeps streaming; that call passes `reason: "refresh"` and reuses the connection's original `Request`, so a cache keyed on request identity **must** be bypassed when `reason === "refresh"` or the re-check silently returns the stale set.
160
160
  - **SSE latency**: because a live connection re-resolves periodically, a grant change (in either direction) takes effect for the stream within ~60s. Events dropped before a widening are not replayed; the next read (which re-resolves per request) restores them.
161
161
 
162
162
  ### Comment visibility
@@ -211,6 +211,7 @@ export default function Route() {
211
211
  user={user}
212
212
  userId={userId}
213
213
  config={{
214
+ onSessionExpired: () => navigate(0), // Required: the host decides the navigation on session expiry
214
215
  renderHeader: ({ title, annotation }) => <YourHeader title={title} annotation={annotation} />,
215
216
  showDevControls: import.meta.env.DEV, // Toggle SSE subscriptions, etc.
216
217
  draftLabelName: "Draft", // Label name for draft states (matches target DB; not tied to locale)
@@ -227,6 +228,8 @@ export default function Route() {
227
228
  }
228
229
  ```
229
230
 
231
+ `config` is **required** and so is its `onSessionExpired` port (omitting it is a type error, TS2741; a non-function throws a `TypeError` at render). The package never navigates by itself: when the session is gone — the SSE stream ends with `auth-required`, or the ids stream answers 401 / 403 — it calls `onSessionExpired()` exactly once per occurrence and stops. A typical host reloads the route (`navigate(0)`) so its own loader redirects to the login page. `DailyReportConfigProvider` takes the same required `config`; outside a provider, the default configuration's `onSessionExpired` throws.
232
+
230
233
  `DailyReportPage` is layout-agnostic (header rendering via `renderHeader` slot, error boundaries/footer control managed by the app). For fine-grained usage, `DailyReportActionProvider`, `DailyReportResolvedContent`, `useDailyReportDetail`, etc. can be imported individually.
231
234
 
232
235
  Mutation failures (save / publish / delete / comment) are surfaced through an error seam: inject `config.onError(info: DailyReportErrorInfo)` to route them into your own toast/notification system, or omit it to use the package's built-in `role="alert"` banner (auto-dismiss + manual close). Failures on a continuation that resolves after a no-reload user switch are suppressed. If you compose `DailyReportActionProvider` yourself instead of using `DailyReportPage`, wrap it in `DailyReportErrorProvider` (both are exported from `@aiquants/daily-report/client`) so `onError` / the banner work.
@@ -525,13 +528,54 @@ export default defineConfig({
525
528
 
526
529
  1. Mutation services publish to Redis Stream (`daily-report:sse-stream`) in sequence: `invalidate -> epoch increment -> SSE publish`.
527
530
  2. `DailyReportSseReader` uses a single blocking `xRead` loop to fan-out events to all SSE connections (eliminating per-connection Redis TCP connections).
528
- 3. `sse.loader` implements catch-up (`xRange`) using `Last-Event-ID` / `lastEventId` and 5-second keep-alive pings. `recipientRawUserId` is filtered server-side and removed before transmission to prevent internal ID leaks.
529
- 4. On the client, `useDailyReportSseConnection` handles exponential backoff reconnects, and the action context correlates optimistic updates with SSE echoes using `clientTempId`.
531
+ 3. `sse.loader` is built on `@aiquants/sse/server` (`createSseResponse`, `terminalStreamResponse`, `startSseHeartbeat`, `readLastEventId`). Its responses carry only `SSE_RESPONSE_HEADERS` plus the forwarded `Set-Cookie` (no `Connection` or other hop-by-hop header, which HTTP/2 forbids). `recipientRawUserId` is filtered server-side and removed before transmission to prevent internal ID leaks. Catch-up and live entries pass through the same filters (recipient, source-type visibility, comment redaction).
532
+ 4. On the client, `useDailyReportSseConnection` runs on `useReopeningEventSource` (`@aiquants/sse/react`): full-jitter backoff from 2 s to 30 s, the failure count reset only after 30 s of healthy open, one native browser reconnect (which carries the cursor in the `Last-Event-ID` header), and a stale watch of 45 s (three heartbeat periods).
533
+ The action context correlates optimistic updates with SSE echoes using `clientTempId`, and exposes the connection status as `sseStatus` (`DailyReportSseConnectionStatus`: the reopening status, or `{ kind: "resyncing" }` while a `resync-required` waits for a fresh ids anchor).
534
+
535
+ ### SSE wire contract
536
+
537
+ Stop signals are **in-band**: the server answers 200 and writes `retry: 86400000` before one named terminal event, so a client that does not listen for the event (an older bundle) reconnects at most once a day instead of in a loop. A non-200 response is reserved for a misconfigured endpoint.
538
+
539
+ | Server-side situation | Response | Client (`useDailyReportSseConnection`) |
540
+ | --- | --- | --- |
541
+ | Not authenticated | 200, `retry: 86400000` → `event: auth-required` (`Set-Cookie` forwarded) | terminated; calls `config.onSessionExpired()` once; never reconnects |
542
+ | Authenticated but no internal user | 200, `retry: 86400000` → `event: forbidden` | terminated; no navigation (a reload cannot fix it); visible through `sseStatus` |
543
+ | Unknown endpoint | 404 | treated as retryable; backoff up to 30 s |
544
+ | Unexpected error before the stream starts (for example the internal-user lookup fails) | 500 with the body `Internal Server Error` (no error detail), `Set-Cookie` forwarded | treated as retryable; backoff up to 30 s |
545
+ | Malformed cursor | 200, `retry: 86400000` → `event: resync-required` | rereads the ids (server cache bypassed) and reopens from the new anchor |
546
+ | Cursor older than the oldest retained entry (the stream is trimmed with `MAXLEN ~ streamMaxLen`) | 200, `connected` → heartbeat → `retry: 86400000` → `event: resync-required` → end | same as above |
547
+ | The start of a catch-up page is trimmed away before the page is read | 200, the entries delivered so far → `retry: 86400000` → `event: resync-required` → end | same as above |
548
+ | Empty stream, or a cursor inside the retained window or in the future | 200, `connected` → heartbeat → catch-up (every entry) → live | continues |
549
+ | Redis failure during the catch-up | the body ends (`producer-failed`) | one native reconnect with `Last-Event-ID`, then the utility's own backoff; the cursor is kept |
550
+
551
+ - **Resume cursor**: `readLastEventId` reads the `Last-Event-ID` header first, then the `lastEventId` query parameter (`DAILY_REPORT_SSE_CURSOR_PARAM`). The browser's own reconnect reuses the URL it opened with (a stale query) but sends a fresh header, so the header is always the newer value. A cursor must be a Redis stream id of the form `<ms>-<seq>` with 1–15 digits per part (`isDailyReportSseStreamId`); anything else is answered with `resync-required`. The client URL-encodes the cursor.
552
+ - **`connected` frame**: `data: {"type":"connected"}` with `id:` set to the resume position (the cursor, or the newest entry at connect time when there is no cursor). On the cursor path it is written before any Redis round trip.
553
+ The same frame is used as an anchor-only frame for entries the viewer's filters drop (addressed to another user, or a source type the viewer cannot see), so the client's cursor keeps advancing without receiving their content.
554
+ Without it, a tab whose visible traffic is quiet while other users' read / star updates flow would keep an old cursor, and its next reconnect would fall outside the retained window (`resync-required`, then an ids rescan that bypasses the server cache).
555
+ - **Catch-up**: entries after the cursor are read in pages of 1,000 (`xRange` is inclusive, so each page skips its start id) until the stream end; every entry is delivered. When a page does not start with its start id, the oldest retained entry is read again: if it is newer than the start id, the range after it may have been trimmed, so the connection ends with `resync-required` instead of silently skipping it (a cursor that is simply not an entry id inside the retained window continues).
556
+ - **Ids on the wire only increase**: live entries that arrive during the catch-up are queued, not written. The catch-up stops in front of the first queued entry (the fan-out reader delivers every entry after it in order), then the queue is written in id order and later live entries are written as they arrive. Writing a live entry first would let a disconnect move the client's resume position past catch-up entries it never received, and an older `report-update` would overwrite a newer one on the client.
557
+ - **Heartbeat**: `startSseHeartbeat` writes a jittered `retry:` (2,000–10,000 ms), an immediate named `event: heartbeat` (`data: {"serverTime":<ms>}`, no `id:`), then one every 15 s (`DAILY_REPORT_SSE_HEARTBEAT_MS`). Named events are ignored by `onmessage`, so old bundles are unaffected.
558
+ - **Stream anchor (`streamAnchor`)**: the ids stream carries the newest SSE entry id on its first authoritative line
559
+ (the line with `total`, or the short-circuit line when the whole data set fits in the fast first page; never on a
560
+ provisional line). The service reads it inside the cached fetcher **before** the SQL, so every cache path (bucket hit,
561
+ joining an in-flight fetch, refresh-ahead) returns an array that already reflects every update up to the anchor. The
562
+ SSE hook does not connect until an anchor arrives and then uses it as the first cursor, which closes the gap between
563
+ loading the ids and opening the stream. `null` means "position unknown" (empty stream, no redis, read failure): the
564
+ hook then opens without a cursor and the server starts from the newest entry at connect time.
565
+ - **Delivery is idempotent**: an anchor can be up to one ids-cache TTL old; replaying from it is safe because `report-*` messages upsert and `comment-add` is matched by comment id.
566
+ - **A resync that gives up is retried**: after `resync-required` the hook asks the ids session for a rescan (`resyncDailyReportIdsStream()`) and does not reconnect until an anchor newer than the one it had arrives. When that rescan gives up (the ids phase stays `complete` with an `error`, which happens after repeated failures during an outage), the hook asks again after a full-jitter backoff from 2 s to 30 s while SSE is enabled, until an anchor arrives. An initial ids scan that ends in `failed` is left to the page's manual retry.
530
567
 
531
568
  ## API Surface (Summary)
532
569
 
533
- - **server**: `createDailyReportServer` / `createDailyReportService` / `createDailyReportHandlers` / `defineDailyReportSchema` / `DailyReportSseReader` / `SqlResultCache` / `transformJsonArray` / `jsonResponseWithETag` / `generateETag` / `isStreamIdLte`
534
- - **client**: `DailyReportPage` / `DailyReportResolvedContent` / `DailyReportList` / `DailyReportDetailList` / `DailyReportActionProvider` / `useDailyReportActionContext` / `useDailyReportDetail` / `useDailyReportPrefetch` / `useDailyReportComments` / `useDailyReportSseConnection` / `useDailyReportIdsStream` / `DailyReportIdsStreamStatus` / `DailyReportIdsStreamClient` / `DailyReportConfigProvider` / `useDailyReportConfig` / `defaultDailyReportClientConfig` / `resolveDailyReportLabels` / `DAILY_REPORT_LABEL_CATALOGS` / `DAILY_REPORT_LABEL_KEYS` / `createDailyReportClientLoader` / `dailyReportShouldRevalidate`
535
- - **shared**: `DailyReportItem` / `DailyReportDetail` / `DailyReportUser` / `dailyReportSseMessageSchema` (8 discriminated union types) / `normalizeBusinessDateKey` / `mergeComments(legacyComments, modernComments, currentUserId, unknownAuthorName)`
570
+ - **server**: `createDailyReportServer` / `createDailyReportService` (`getDailyReportIdsByExternalId` returns `{ items, streamAnchor }`; `readSseStreamAnchor`) / `createDailyReportHandlers` / `DAILY_REPORT_SSE_VISIBILITY_REFRESH_MS` / `DAILY_REPORT_SSE_CATCH_UP_BATCH` / `defineDailyReportSchema` / `DailyReportSseReader` / `SqlResultCache` / `transformJsonArray` / `jsonResponseWithETag` / `generateETag` / `isStreamIdLte`
571
+ - **client**: `DailyReportPage` / `DailyReportResolvedContent` / `DailyReportList` / `DailyReportDetailList` /
572
+ `DailyReportActionProvider` / `useDailyReportActionContext` (`sseStatus`) / `useDailyReportDetail` /
573
+ `useDailyReportPrefetch` / `useDailyReportComments` / `useDailyReportSseConnection` (returns
574
+ `DailyReportSseConnectionStatus`) / `useDailyReportIdsStream` (state carries `streamAnchor`) /
575
+ `resyncDailyReportIdsStream` / `DailyReportIdsStreamStatus` / `DailyReportIdsStreamClient` (`resync()`) /
576
+ `DailyReportConfigProvider` / `useDailyReportConfig` / `defaultDailyReportClientConfig` / `resolveDailyReportLabels` /
577
+ `DAILY_REPORT_LABEL_CATALOGS` / `DAILY_REPORT_LABEL_KEYS` / `createDailyReportClientLoader` /
578
+ `dailyReportShouldRevalidate`
579
+ - **shared**: `DailyReportItem` / `DailyReportDetail` / `DailyReportUser` / `dailyReportSseMessageSchema` (8 discriminated union types) / `DAILY_REPORT_SSE_TERMINAL_EVENTS` / `DAILY_REPORT_SSE_HEARTBEAT_MS` / `DAILY_REPORT_SSE_CURSOR_PARAM` / `isDailyReportSseStreamId` / `DailyReportSseStreamAnchor` / `normalizeBusinessDateKey` / `mergeComments(legacyComments, modernComments, currentUserId, unknownAuthorName)`
536
580
 
537
581
  MIT
package/dist/client.d.mts CHANGED
@@ -2,7 +2,8 @@ import * as react from "react";
2
2
  import { RefObject, ReactNode, Context } from "react";
3
3
  import { ScrollBarThumbOverlayRenderProps, VirtualScrollLocale, VirtualScrollLabels, ScrollPaneInertiaOptions, ScrollBarTapCircleOptions } from "@aiquants/virtualscroll";
4
4
  import { D as DailyReportAttachmentSummary, a as DailyReportItem, b as DailyReportDetail, c as DailyReportUser } from "./types-CqgYQgbH.mjs";
5
- import { U as UIComment, D as DailyReportSseMessage } from "./sse-schema-DCxpdETM.mjs";
5
+ import { U as UIComment, D as DailyReportSseTerminalEvent, a as DailyReportSseMessage, b as DailyReportSseStreamAnchor } from "./ids-stream-DCuiaTit.mjs";
6
+ import { UseReopeningEventSourceResult } from "@aiquants/sse/react";
6
7
  import { ShouldRevalidateFunction } from "react-router";
7
8
  import "zod";
8
9
  declare const BusinessDayThumbOverlay: ({ orientation, thumbCenter, label, isDragging, isTapScrollActive, isWheelScrollActive }: ScrollBarThumbOverlayRenderProps & {
@@ -282,15 +283,17 @@ type DailyReportClientConfig = {
282
283
  renderHeader: (props: DailyReportHeaderProps) => ReactNode;
283
284
  showDevControls: boolean;
284
285
  onError?: (info: DailyReportErrorInfo) => void;
286
+ onSessionExpired: () => void;
285
287
  };
286
- type DailyReportClientConfigInput = Partial<Omit<DailyReportClientConfig, "locale" | "labels">> & {
288
+ type DailyReportClientConfigInput = Partial<Omit<DailyReportClientConfig, "locale" | "labels" | "onSessionExpired">> & {
287
289
  locale?: DailyReportLocale;
288
290
  labels?: DailyReportLabelOverrides;
291
+ onSessionExpired: () => void;
289
292
  };
290
293
  type DailyReportClientConfigDefaults = Readonly<Pick<DailyReportClientConfig, "apiBasePath" | "ssePath" | "draftLabelName" | "renderHeader" | "showDevControls">>;
291
294
  declare const defaultDailyReportClientConfig: DailyReportClientConfigDefaults;
292
295
  declare const DailyReportConfigProvider: ({ config, children }: {
293
- config?: DailyReportClientConfigInput;
296
+ config: DailyReportClientConfigInput;
294
297
  children: ReactNode;
295
298
  }) => react.JSX.Element;
296
299
  declare const useDailyReportConfig: () => DailyReportClientConfig;
@@ -300,7 +303,7 @@ declare const DailyReportLoading: () => react.JSX.Element;
300
303
  type DailyReportPageProps = {
301
304
  user?: DailyReportUser;
302
305
  userId?: string | null;
303
- config?: DailyReportClientConfigInput;
306
+ config: DailyReportClientConfigInput;
304
307
  };
305
308
  declare const DailyReportPage: ({ config, ...rest }: DailyReportPageProps) => react.JSX.Element;
306
309
  type DailyReportResolvedContentProps = {
@@ -311,6 +314,16 @@ type UnreadIndicatorProps = {
311
314
  className?: string;
312
315
  };
313
316
  declare const UnreadIndicator: ({ className }: UnreadIndicatorProps) => react.JSX.Element;
317
+ type DailyReportSseConnectionStatus = UseReopeningEventSourceResult<DailyReportSseTerminalEvent>["status"] | {
318
+ readonly kind: "resyncing";
319
+ };
320
+ type UseDailyReportSseConnectionOptions = {
321
+ isSseEnabled: boolean;
322
+ onMessage: (payload: Exclude<DailyReportSseMessage, {
323
+ type: "connected";
324
+ }>) => void;
325
+ };
326
+ declare const useDailyReportSseConnection: ({ isSseEnabled, onMessage }: UseDailyReportSseConnectionOptions) => DailyReportSseConnectionStatus;
314
327
  type DailyReportActionContextType = {
315
328
  user?: DailyReportUser;
316
329
  pendingAddCommentIds: React.MutableRefObject<Map<number, Map<number, string>>>;
@@ -322,6 +335,7 @@ type DailyReportActionContextType = {
322
335
  items: DailyReportItem[];
323
336
  isSseEnabled: boolean;
324
337
  toggleSse: () => void;
338
+ sseStatus: DailyReportSseConnectionStatus;
325
339
  addComment: (reportHubId: number, content: string, businessDate: string) => Promise<void>;
326
340
  deleteComment: (reportHubId: number, commentId: number, businessDate: string) => Promise<void>;
327
341
  toggleStar: (reportHubId: number, isStarred: boolean, businessDate: string) => Promise<void>;
@@ -420,13 +434,6 @@ declare const useDailyReportComments: (report: DailyReportDetail | null, user: U
420
434
  setPendingDeleteId: react.Dispatch<react.SetStateAction<string | number | null>>;
421
435
  resolvedIdMap: Map<number, number>;
422
436
  };
423
- type UseDailyReportSseConnectionOptions = {
424
- isSseEnabled: boolean;
425
- onMessage: (payload: Exclude<DailyReportSseMessage, {
426
- type: "connected";
427
- }>) => void;
428
- };
429
- declare const useDailyReportSseConnection: ({ isSseEnabled, onMessage }: UseDailyReportSseConnectionOptions) => void;
430
437
  type UseDynamicViewportHeightOptions = {
431
438
  enableMutationObserver?: boolean;
432
439
  bottomGap?: number;
@@ -456,6 +463,7 @@ type DailyReportIdsStreamState = {
456
463
  hasCompletedOnce: boolean;
457
464
  completedAt: number | null;
458
465
  error: Error | null;
466
+ streamAnchor: DailyReportSseStreamAnchor | null;
459
467
  };
460
468
  type DailyReportIdsStreamClientOptions = {
461
469
  apiBasePath?: string;
@@ -480,6 +488,7 @@ declare class DailyReportIdsStreamClient {
480
488
  private readonly itemMap;
481
489
  private readonly listeners;
482
490
  private state;
491
+ private streamAnchorRevision;
483
492
  private lastCursor;
484
493
  private lastSnapshot;
485
494
  private consecutiveFailures;
@@ -503,6 +512,7 @@ declare class DailyReportIdsStreamClient {
503
512
  refresh(options?: {
504
513
  forceRefresh?: boolean;
505
514
  }): void;
515
+ resync(): void;
506
516
  applyExternalUpsert(item: DailyReportItem): void;
507
517
  applyExternalRemoval(reportHubId: number): void;
508
518
  dispose(): void;
@@ -511,6 +521,7 @@ declare class DailyReportIdsStreamClient {
511
521
  private streamOnce;
512
522
  private readWithStallGuard;
513
523
  private absorbChunkLine;
524
+ private absorbStreamAnchor;
514
525
  private publishItems;
515
526
  private schedulePublishItems;
516
527
  private cancelScheduledPublishItems;
@@ -518,7 +529,6 @@ declare class DailyReportIdsStreamClient {
518
529
  private buildStreamUrl;
519
530
  private waitBeforeRetry;
520
531
  }
521
- declare const reloadDocument: () => void;
522
532
  declare const ensureDailyReportIdsStreamSession: (options: {
523
533
  apiBasePath?: string;
524
534
  userKey: string | null;
@@ -531,6 +541,7 @@ declare const primeDailyReportIdsStreamSession: () => void;
531
541
  declare const upsertDailyReportIdsStreamItem: (item: DailyReportItem) => void;
532
542
  declare const removeDailyReportIdsStreamItem: (reportHubId: number) => void;
533
543
  declare const retryDailyReportIdsStream: () => void;
544
+ declare const resyncDailyReportIdsStream: () => void;
534
545
  declare const refreshDailyReportIdsStream: (options?: {
535
546
  forceRefresh?: boolean;
536
547
  }) => void;
@@ -557,4 +568,4 @@ declare const CONTENT_DRAG_POINTER_INPUTS: readonly [
557
568
  "pen",
558
569
  "touch"
559
570
  ];
560
- export { BusinessDayThumbOverlay, CONTENT_DRAG_POINTER_INPUTS, DAILY_REPORT_FETCH_DEBOUNCE_MS, DAILY_REPORT_LABEL_CATALOGS, DAILY_REPORT_LABEL_KEYS, DEFAULT_ITEM_HEIGHT, type DailyReportActionContextType, DailyReportActionProvider, DailyReportAttachmentIndicator, DailyReportAttachmentList, DailyReportCache, type DailyReportClientConfig, type DailyReportClientConfigDefaults, type DailyReportClientConfigInput, DailyReportCommentForm, DailyReportCommentList, DailyReportConfigProvider, DailyReportDetailList, type DailyReportDetailListScrollRestore, type DailyReportDetailResource, DailyReportEditForm, type DailyReportErrorInfo, DailyReportErrorProvider, type DailyReportHeaderProps, DailyReportIdsStreamClient, type DailyReportIdsStreamClientOptions, type DailyReportIdsStreamPhase, type DailyReportIdsStreamState, DailyReportIdsStreamStatus, type DailyReportLabelOverrides, type DailyReportLabels, DailyReportList, DailyReportLoadError, DailyReportLoading, type DailyReportLocale, type DailyReportOperation, DailyReportPage, type DailyReportPageProps, DailyReportPostedComment, DailyReportResolvedContent, EMPTY_DETAIL, EMPTY_ITEM, FAST_SCROLL_INERTIA_OPTIONS, FAST_SCROLL_WHEEL_MULTIPLIER, ITEM_CONTAINER_HEIGHT, ITEM_VERTICAL_PADDING, MAX_PREVIEW_LINES, PREFETCH_LOOKAHEAD, PREFETCH_MAX_DATES, SCROLL_BAR_WIDTH, SSE_INITIAL_CONNECT_DEFER_MS, type SourceTypeConfig, TAP_SCROLL_CIRCLE_OPTIONS, UnreadIndicator, VIRTUAL_SCROLL_OVERSCAN_COUNT, WHEEL_RESET_TIMEOUT_MS, acquireMutationLock, applyBroadcastReportUpdate, applyDailyReportServerUpdates, bootstrapDailyReportIdsStreamSession, clearDailyReportCache, createDailyReportClientLoader, dailyReportShouldRevalidate, defaultDailyReportClientConfig, deleteDailyReportCache, ensureDailyReportIdsStreamSession, getCachedReport, getDailyReportIdsStreamSessionUserKey, primeDailyReportIdsStreamSession, refreshDailyReportIdsStream, registerRecentDeletion, releaseMutationLock, reloadDocument, removeDailyReportIdsStreamItem, resetDailyReportIdsStreamSessionForTesting, resolveDailyReportLabels, resolveSourceTypeConfig, retryDailyReportIdsStream, subscribeCacheReady, updateDailyReportCache, upsertDailyReportIdsStreamItem, useAnimatedNumber, useDailyReportActionContext, useDailyReportComments, useDailyReportConfig, useDailyReportDetail, useDailyReportErrorSurface, useDailyReportIdsStream, useDailyReportPrefetch, useDailyReportSseConnection, useDynamicViewportHeight, writeCache };
571
+ export { BusinessDayThumbOverlay, CONTENT_DRAG_POINTER_INPUTS, DAILY_REPORT_FETCH_DEBOUNCE_MS, DAILY_REPORT_LABEL_CATALOGS, DAILY_REPORT_LABEL_KEYS, DEFAULT_ITEM_HEIGHT, type DailyReportActionContextType, DailyReportActionProvider, DailyReportAttachmentIndicator, DailyReportAttachmentList, DailyReportCache, type DailyReportClientConfig, type DailyReportClientConfigDefaults, type DailyReportClientConfigInput, DailyReportCommentForm, DailyReportCommentList, DailyReportConfigProvider, DailyReportDetailList, type DailyReportDetailListScrollRestore, type DailyReportDetailResource, DailyReportEditForm, type DailyReportErrorInfo, DailyReportErrorProvider, type DailyReportHeaderProps, DailyReportIdsStreamClient, type DailyReportIdsStreamClientOptions, type DailyReportIdsStreamPhase, type DailyReportIdsStreamState, DailyReportIdsStreamStatus, type DailyReportLabelOverrides, type DailyReportLabels, DailyReportList, DailyReportLoadError, DailyReportLoading, type DailyReportLocale, type DailyReportOperation, DailyReportPage, type DailyReportPageProps, DailyReportPostedComment, DailyReportResolvedContent, type DailyReportSseConnectionStatus, EMPTY_DETAIL, EMPTY_ITEM, FAST_SCROLL_INERTIA_OPTIONS, FAST_SCROLL_WHEEL_MULTIPLIER, ITEM_CONTAINER_HEIGHT, ITEM_VERTICAL_PADDING, MAX_PREVIEW_LINES, PREFETCH_LOOKAHEAD, PREFETCH_MAX_DATES, SCROLL_BAR_WIDTH, SSE_INITIAL_CONNECT_DEFER_MS, type SourceTypeConfig, TAP_SCROLL_CIRCLE_OPTIONS, UnreadIndicator, type UseDailyReportSseConnectionOptions, VIRTUAL_SCROLL_OVERSCAN_COUNT, WHEEL_RESET_TIMEOUT_MS, acquireMutationLock, applyBroadcastReportUpdate, applyDailyReportServerUpdates, bootstrapDailyReportIdsStreamSession, clearDailyReportCache, createDailyReportClientLoader, dailyReportShouldRevalidate, defaultDailyReportClientConfig, deleteDailyReportCache, ensureDailyReportIdsStreamSession, getCachedReport, getDailyReportIdsStreamSessionUserKey, primeDailyReportIdsStreamSession, refreshDailyReportIdsStream, registerRecentDeletion, releaseMutationLock, removeDailyReportIdsStreamItem, resetDailyReportIdsStreamSessionForTesting, resolveDailyReportLabels, resolveSourceTypeConfig, resyncDailyReportIdsStream, retryDailyReportIdsStream, subscribeCacheReady, updateDailyReportCache, upsertDailyReportIdsStreamItem, useAnimatedNumber, useDailyReportActionContext, useDailyReportComments, useDailyReportConfig, useDailyReportDetail, useDailyReportErrorSurface, useDailyReportIdsStream, useDailyReportPrefetch, useDailyReportSseConnection, useDynamicViewportHeight, writeCache };
package/dist/client.d.ts CHANGED
@@ -2,7 +2,8 @@ import * as react from "react";
2
2
  import { RefObject, ReactNode, Context } from "react";
3
3
  import { ScrollBarThumbOverlayRenderProps, VirtualScrollLocale, VirtualScrollLabels, ScrollPaneInertiaOptions, ScrollBarTapCircleOptions } from "@aiquants/virtualscroll";
4
4
  import { D as DailyReportAttachmentSummary, a as DailyReportItem, b as DailyReportDetail, c as DailyReportUser } from "./types-CqgYQgbH.js";
5
- import { U as UIComment, D as DailyReportSseMessage } from "./sse-schema-DoQw8_om.js";
5
+ import { U as UIComment, D as DailyReportSseTerminalEvent, a as DailyReportSseMessage, b as DailyReportSseStreamAnchor } from "./ids-stream-BnM6L-Sk.js";
6
+ import { UseReopeningEventSourceResult } from "@aiquants/sse/react";
6
7
  import { ShouldRevalidateFunction } from "react-router";
7
8
  import "zod";
8
9
  declare const BusinessDayThumbOverlay: ({ orientation, thumbCenter, label, isDragging, isTapScrollActive, isWheelScrollActive }: ScrollBarThumbOverlayRenderProps & {
@@ -282,15 +283,17 @@ type DailyReportClientConfig = {
282
283
  renderHeader: (props: DailyReportHeaderProps) => ReactNode;
283
284
  showDevControls: boolean;
284
285
  onError?: (info: DailyReportErrorInfo) => void;
286
+ onSessionExpired: () => void;
285
287
  };
286
- type DailyReportClientConfigInput = Partial<Omit<DailyReportClientConfig, "locale" | "labels">> & {
288
+ type DailyReportClientConfigInput = Partial<Omit<DailyReportClientConfig, "locale" | "labels" | "onSessionExpired">> & {
287
289
  locale?: DailyReportLocale;
288
290
  labels?: DailyReportLabelOverrides;
291
+ onSessionExpired: () => void;
289
292
  };
290
293
  type DailyReportClientConfigDefaults = Readonly<Pick<DailyReportClientConfig, "apiBasePath" | "ssePath" | "draftLabelName" | "renderHeader" | "showDevControls">>;
291
294
  declare const defaultDailyReportClientConfig: DailyReportClientConfigDefaults;
292
295
  declare const DailyReportConfigProvider: ({ config, children }: {
293
- config?: DailyReportClientConfigInput;
296
+ config: DailyReportClientConfigInput;
294
297
  children: ReactNode;
295
298
  }) => react.JSX.Element;
296
299
  declare const useDailyReportConfig: () => DailyReportClientConfig;
@@ -300,7 +303,7 @@ declare const DailyReportLoading: () => react.JSX.Element;
300
303
  type DailyReportPageProps = {
301
304
  user?: DailyReportUser;
302
305
  userId?: string | null;
303
- config?: DailyReportClientConfigInput;
306
+ config: DailyReportClientConfigInput;
304
307
  };
305
308
  declare const DailyReportPage: ({ config, ...rest }: DailyReportPageProps) => react.JSX.Element;
306
309
  type DailyReportResolvedContentProps = {
@@ -311,6 +314,16 @@ type UnreadIndicatorProps = {
311
314
  className?: string;
312
315
  };
313
316
  declare const UnreadIndicator: ({ className }: UnreadIndicatorProps) => react.JSX.Element;
317
+ type DailyReportSseConnectionStatus = UseReopeningEventSourceResult<DailyReportSseTerminalEvent>["status"] | {
318
+ readonly kind: "resyncing";
319
+ };
320
+ type UseDailyReportSseConnectionOptions = {
321
+ isSseEnabled: boolean;
322
+ onMessage: (payload: Exclude<DailyReportSseMessage, {
323
+ type: "connected";
324
+ }>) => void;
325
+ };
326
+ declare const useDailyReportSseConnection: ({ isSseEnabled, onMessage }: UseDailyReportSseConnectionOptions) => DailyReportSseConnectionStatus;
314
327
  type DailyReportActionContextType = {
315
328
  user?: DailyReportUser;
316
329
  pendingAddCommentIds: React.MutableRefObject<Map<number, Map<number, string>>>;
@@ -322,6 +335,7 @@ type DailyReportActionContextType = {
322
335
  items: DailyReportItem[];
323
336
  isSseEnabled: boolean;
324
337
  toggleSse: () => void;
338
+ sseStatus: DailyReportSseConnectionStatus;
325
339
  addComment: (reportHubId: number, content: string, businessDate: string) => Promise<void>;
326
340
  deleteComment: (reportHubId: number, commentId: number, businessDate: string) => Promise<void>;
327
341
  toggleStar: (reportHubId: number, isStarred: boolean, businessDate: string) => Promise<void>;
@@ -420,13 +434,6 @@ declare const useDailyReportComments: (report: DailyReportDetail | null, user: U
420
434
  setPendingDeleteId: react.Dispatch<react.SetStateAction<string | number | null>>;
421
435
  resolvedIdMap: Map<number, number>;
422
436
  };
423
- type UseDailyReportSseConnectionOptions = {
424
- isSseEnabled: boolean;
425
- onMessage: (payload: Exclude<DailyReportSseMessage, {
426
- type: "connected";
427
- }>) => void;
428
- };
429
- declare const useDailyReportSseConnection: ({ isSseEnabled, onMessage }: UseDailyReportSseConnectionOptions) => void;
430
437
  type UseDynamicViewportHeightOptions = {
431
438
  enableMutationObserver?: boolean;
432
439
  bottomGap?: number;
@@ -456,6 +463,7 @@ type DailyReportIdsStreamState = {
456
463
  hasCompletedOnce: boolean;
457
464
  completedAt: number | null;
458
465
  error: Error | null;
466
+ streamAnchor: DailyReportSseStreamAnchor | null;
459
467
  };
460
468
  type DailyReportIdsStreamClientOptions = {
461
469
  apiBasePath?: string;
@@ -480,6 +488,7 @@ declare class DailyReportIdsStreamClient {
480
488
  private readonly itemMap;
481
489
  private readonly listeners;
482
490
  private state;
491
+ private streamAnchorRevision;
483
492
  private lastCursor;
484
493
  private lastSnapshot;
485
494
  private consecutiveFailures;
@@ -503,6 +512,7 @@ declare class DailyReportIdsStreamClient {
503
512
  refresh(options?: {
504
513
  forceRefresh?: boolean;
505
514
  }): void;
515
+ resync(): void;
506
516
  applyExternalUpsert(item: DailyReportItem): void;
507
517
  applyExternalRemoval(reportHubId: number): void;
508
518
  dispose(): void;
@@ -511,6 +521,7 @@ declare class DailyReportIdsStreamClient {
511
521
  private streamOnce;
512
522
  private readWithStallGuard;
513
523
  private absorbChunkLine;
524
+ private absorbStreamAnchor;
514
525
  private publishItems;
515
526
  private schedulePublishItems;
516
527
  private cancelScheduledPublishItems;
@@ -518,7 +529,6 @@ declare class DailyReportIdsStreamClient {
518
529
  private buildStreamUrl;
519
530
  private waitBeforeRetry;
520
531
  }
521
- declare const reloadDocument: () => void;
522
532
  declare const ensureDailyReportIdsStreamSession: (options: {
523
533
  apiBasePath?: string;
524
534
  userKey: string | null;
@@ -531,6 +541,7 @@ declare const primeDailyReportIdsStreamSession: () => void;
531
541
  declare const upsertDailyReportIdsStreamItem: (item: DailyReportItem) => void;
532
542
  declare const removeDailyReportIdsStreamItem: (reportHubId: number) => void;
533
543
  declare const retryDailyReportIdsStream: () => void;
544
+ declare const resyncDailyReportIdsStream: () => void;
534
545
  declare const refreshDailyReportIdsStream: (options?: {
535
546
  forceRefresh?: boolean;
536
547
  }) => void;
@@ -557,4 +568,4 @@ declare const CONTENT_DRAG_POINTER_INPUTS: readonly [
557
568
  "pen",
558
569
  "touch"
559
570
  ];
560
- export { BusinessDayThumbOverlay, CONTENT_DRAG_POINTER_INPUTS, DAILY_REPORT_FETCH_DEBOUNCE_MS, DAILY_REPORT_LABEL_CATALOGS, DAILY_REPORT_LABEL_KEYS, DEFAULT_ITEM_HEIGHT, type DailyReportActionContextType, DailyReportActionProvider, DailyReportAttachmentIndicator, DailyReportAttachmentList, DailyReportCache, type DailyReportClientConfig, type DailyReportClientConfigDefaults, type DailyReportClientConfigInput, DailyReportCommentForm, DailyReportCommentList, DailyReportConfigProvider, DailyReportDetailList, type DailyReportDetailListScrollRestore, type DailyReportDetailResource, DailyReportEditForm, type DailyReportErrorInfo, DailyReportErrorProvider, type DailyReportHeaderProps, DailyReportIdsStreamClient, type DailyReportIdsStreamClientOptions, type DailyReportIdsStreamPhase, type DailyReportIdsStreamState, DailyReportIdsStreamStatus, type DailyReportLabelOverrides, type DailyReportLabels, DailyReportList, DailyReportLoadError, DailyReportLoading, type DailyReportLocale, type DailyReportOperation, DailyReportPage, type DailyReportPageProps, DailyReportPostedComment, DailyReportResolvedContent, EMPTY_DETAIL, EMPTY_ITEM, FAST_SCROLL_INERTIA_OPTIONS, FAST_SCROLL_WHEEL_MULTIPLIER, ITEM_CONTAINER_HEIGHT, ITEM_VERTICAL_PADDING, MAX_PREVIEW_LINES, PREFETCH_LOOKAHEAD, PREFETCH_MAX_DATES, SCROLL_BAR_WIDTH, SSE_INITIAL_CONNECT_DEFER_MS, type SourceTypeConfig, TAP_SCROLL_CIRCLE_OPTIONS, UnreadIndicator, VIRTUAL_SCROLL_OVERSCAN_COUNT, WHEEL_RESET_TIMEOUT_MS, acquireMutationLock, applyBroadcastReportUpdate, applyDailyReportServerUpdates, bootstrapDailyReportIdsStreamSession, clearDailyReportCache, createDailyReportClientLoader, dailyReportShouldRevalidate, defaultDailyReportClientConfig, deleteDailyReportCache, ensureDailyReportIdsStreamSession, getCachedReport, getDailyReportIdsStreamSessionUserKey, primeDailyReportIdsStreamSession, refreshDailyReportIdsStream, registerRecentDeletion, releaseMutationLock, reloadDocument, removeDailyReportIdsStreamItem, resetDailyReportIdsStreamSessionForTesting, resolveDailyReportLabels, resolveSourceTypeConfig, retryDailyReportIdsStream, subscribeCacheReady, updateDailyReportCache, upsertDailyReportIdsStreamItem, useAnimatedNumber, useDailyReportActionContext, useDailyReportComments, useDailyReportConfig, useDailyReportDetail, useDailyReportErrorSurface, useDailyReportIdsStream, useDailyReportPrefetch, useDailyReportSseConnection, useDynamicViewportHeight, writeCache };
571
+ export { BusinessDayThumbOverlay, CONTENT_DRAG_POINTER_INPUTS, DAILY_REPORT_FETCH_DEBOUNCE_MS, DAILY_REPORT_LABEL_CATALOGS, DAILY_REPORT_LABEL_KEYS, DEFAULT_ITEM_HEIGHT, type DailyReportActionContextType, DailyReportActionProvider, DailyReportAttachmentIndicator, DailyReportAttachmentList, DailyReportCache, type DailyReportClientConfig, type DailyReportClientConfigDefaults, type DailyReportClientConfigInput, DailyReportCommentForm, DailyReportCommentList, DailyReportConfigProvider, DailyReportDetailList, type DailyReportDetailListScrollRestore, type DailyReportDetailResource, DailyReportEditForm, type DailyReportErrorInfo, DailyReportErrorProvider, type DailyReportHeaderProps, DailyReportIdsStreamClient, type DailyReportIdsStreamClientOptions, type DailyReportIdsStreamPhase, type DailyReportIdsStreamState, DailyReportIdsStreamStatus, type DailyReportLabelOverrides, type DailyReportLabels, DailyReportList, DailyReportLoadError, DailyReportLoading, type DailyReportLocale, type DailyReportOperation, DailyReportPage, type DailyReportPageProps, DailyReportPostedComment, DailyReportResolvedContent, type DailyReportSseConnectionStatus, EMPTY_DETAIL, EMPTY_ITEM, FAST_SCROLL_INERTIA_OPTIONS, FAST_SCROLL_WHEEL_MULTIPLIER, ITEM_CONTAINER_HEIGHT, ITEM_VERTICAL_PADDING, MAX_PREVIEW_LINES, PREFETCH_LOOKAHEAD, PREFETCH_MAX_DATES, SCROLL_BAR_WIDTH, SSE_INITIAL_CONNECT_DEFER_MS, type SourceTypeConfig, TAP_SCROLL_CIRCLE_OPTIONS, UnreadIndicator, type UseDailyReportSseConnectionOptions, VIRTUAL_SCROLL_OVERSCAN_COUNT, WHEEL_RESET_TIMEOUT_MS, acquireMutationLock, applyBroadcastReportUpdate, applyDailyReportServerUpdates, bootstrapDailyReportIdsStreamSession, clearDailyReportCache, createDailyReportClientLoader, dailyReportShouldRevalidate, defaultDailyReportClientConfig, deleteDailyReportCache, ensureDailyReportIdsStreamSession, getCachedReport, getDailyReportIdsStreamSessionUserKey, primeDailyReportIdsStreamSession, refreshDailyReportIdsStream, registerRecentDeletion, releaseMutationLock, removeDailyReportIdsStreamItem, resetDailyReportIdsStreamSessionForTesting, resolveDailyReportLabels, resolveSourceTypeConfig, resyncDailyReportIdsStream, retryDailyReportIdsStream, subscribeCacheReady, updateDailyReportCache, upsertDailyReportIdsStreamItem, useAnimatedNumber, useDailyReportActionContext, useDailyReportComments, useDailyReportConfig, useDailyReportDetail, useDailyReportErrorSurface, useDailyReportIdsStream, useDailyReportPrefetch, useDailyReportSseConnection, useDynamicViewportHeight, writeCache };