@ai-matrx/data 0.16.1 → 0.16.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.2
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/data/v0.16.1..npm/data/v0.16.2 -- apps/shared/data`).
7
+ No source changes intended and no consumer action required.
8
+
3
9
  ## 0.16.1
4
10
 
5
11
  Automatic changed-only republish (docs/metadata drift since the last tag — see
package/dist/react.cjs CHANGED
@@ -559,6 +559,62 @@ function usePaginatedData(options) {
559
559
  }
560
560
  }
561
561
  }, [isCurrent, publish]);
562
+ const refreshLoadedWindow = (0, import_react5.useCallback)(async () => {
563
+ const current = snapshotRef.current;
564
+ const requestKey = queryKeyRef.current;
565
+ const requestInitialCursor = initialCursorRef.current;
566
+ if (inFlightRef.current || !enabledRef.current || current.queryKey !== requestKey || current.initialCursor !== requestInitialCursor || current.phase === "initial") return;
567
+ const generation = generationRef.current;
568
+ const controller = new AbortController();
569
+ controllerRef.current = controller;
570
+ inFlightRef.current = true;
571
+ publish({ ...current, error: null, phase: "refreshing" });
572
+ try {
573
+ await optionsRef.current.onBeforeBackgroundRefresh?.();
574
+ if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;
575
+ const targetRowCount = current.rows.length;
576
+ const receipts = /* @__PURE__ */ new Set();
577
+ let cursor = requestInitialCursor;
578
+ let rows = [];
579
+ let nextCursor = null;
580
+ let totalItems = current.totalItems;
581
+ while (true) {
582
+ const page = await optionsRef.current.loadPage({ cursor, signal: controller.signal });
583
+ if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;
584
+ rows = mergeRows(rows, page.rows, optionsRef.current.getRowId);
585
+ receipts.add(cursor);
586
+ totalItems = page.totalItems ?? totalItems;
587
+ nextCursor = page.nextCursor;
588
+ if (nextCursor !== null && receipts.has(nextCursor)) {
589
+ throw new Error(`Pagination source returned cursor "${String(nextCursor)}" more than once; background refresh stopped to prevent a duplicate-page loop.`);
590
+ }
591
+ if (rows.length >= targetRowCount || nextCursor === null) break;
592
+ cursor = nextCursor;
593
+ }
594
+ if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;
595
+ receiptsRef.current = receipts;
596
+ publish({
597
+ queryKey: requestKey,
598
+ initialCursor: requestInitialCursor,
599
+ rows,
600
+ nextCursor,
601
+ totalItems,
602
+ error: null,
603
+ phase: "ready"
604
+ });
605
+ } catch (raw) {
606
+ if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal) || isAbort2(raw)) return;
607
+ const error = pgErrorToError(raw);
608
+ publish({ ...current, error, phase: "refresh-error" });
609
+ setLoadAllRequested(false);
610
+ optionsRef.current.onError?.(error);
611
+ } finally {
612
+ if (isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) {
613
+ inFlightRef.current = false;
614
+ controllerRef.current = null;
615
+ }
616
+ }
617
+ }, [isCurrent, publish]);
562
618
  (0, import_react5.useEffect)(() => {
563
619
  generationRef.current += 1;
564
620
  const generation = generationRef.current;
@@ -585,7 +641,7 @@ function usePaginatedData(options) {
585
641
  await requestPage(optionsRef.current.initialCursor, "initial", requestKey, optionsRef.current.initialCursor, generation);
586
642
  return;
587
643
  }
588
- if (current.phase === "cursor-error" || current.nextCursor === null) return;
644
+ if (current.phase === "cursor-error" || current.phase === "refresh-error" || current.nextCursor === null) return;
589
645
  if (receiptsRef.current.has(current.nextCursor)) {
590
646
  const error = new Error(`Pagination source tried to load cursor "${String(current.nextCursor)}" twice; loading stopped to prevent a duplicate-page loop.`);
591
647
  publish({ ...current, error, nextCursor: null, phase: "cursor-error" });
@@ -597,11 +653,20 @@ function usePaginatedData(options) {
597
653
  const refresh = (0, import_react5.useCallback)(() => setRefreshTick((tick) => tick + 1), []);
598
654
  const requestLoadAll = (0, import_react5.useCallback)(() => setLoadAllRequested(true), []);
599
655
  const stopLoadingAll = (0, import_react5.useCallback)(() => setLoadAllRequested(false), []);
656
+ (0, import_react5.useEffect)(() => {
657
+ const interval = options.refreshIntervalMs;
658
+ if (!enabled || interval === void 0 || !Number.isFinite(interval) || interval <= 0) return void 0;
659
+ const timer = window.setInterval(() => {
660
+ if (typeof document !== "undefined" && document.hidden) return;
661
+ void refreshLoadedWindow();
662
+ }, interval);
663
+ return () => window.clearInterval(timer);
664
+ }, [enabled, options.refreshIntervalMs, refreshLoadedWindow]);
600
665
  (0, import_react5.useEffect)(() => {
601
666
  if (!loadAllRequested || inFlightRef.current) return;
602
667
  const current = snapshotRef.current;
603
668
  if (current.phase === "ready" && current.nextCursor !== null) void loadNextPage();
604
- else if (current.phase === "cursor-error" || current.nextCursor === null) setLoadAllRequested(false);
669
+ else if (current.phase === "cursor-error" || current.phase === "refresh-error" || current.nextCursor === null) setLoadAllRequested(false);
605
670
  }, [loadAllRequested, loadNextPage, snapshot]);
606
671
  const visible = enabled && snapshot.queryKey === queryKey && snapshot.initialCursor === initialCursor;
607
672
  const phase = visible ? snapshot.phase : "initial";
@@ -609,9 +674,10 @@ function usePaginatedData(options) {
609
674
  queryKey,
610
675
  rows: visible ? snapshot.rows : [],
611
676
  loading: enabled && phase === "initial",
677
+ isRefreshing: visible && phase === "refreshing",
612
678
  isFetchingNextPage: visible && phase === "next",
613
679
  error: visible ? snapshot.error : null,
614
- hasNextPage: visible && phase !== "cursor-error" && (snapshot.nextCursor !== null || phase === "initial-error"),
680
+ hasNextPage: visible && phase !== "cursor-error" && phase !== "refresh-error" && (snapshot.nextCursor !== null || phase === "initial-error"),
615
681
  loadNextPage,
616
682
  refresh,
617
683
  totalItems: visible ? snapshot.totalItems : void 0,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts","../src/react/use-async-data.ts","../src/net/errors.ts","../src/index.ts","../src/react/use-realtime-channel.ts","../src/db/realtime.ts","../src/react/use-realtime-query.ts","../src/react/use-async-action.ts","../src/react/use-paginated-data.ts","../src/pagination/scroll-policy.ts"],"sourcesContent":["\"use client\";\n\n/**\n * `@ai-matrx/data/react` — the React bindings for the data layer.\n *\n * Three hooks, each built at a MEASURED repeated consumer (C25 — no\n * speculation): a read (`useAsyncData`, 93 hand-wired copies), a live read\n * (`useRealtimeChannel` / `useRealtimeQuery`, 18 hand-wired copies), and a\n * write (`useAsyncAction`, 554 hand-wired pending booleans). Every hook's\n * header names the specific production defects its hand-rolled twins carry.\n *\n * This is NOT a data-fetching framework and never becomes one. It holds no\n * cache, no store, no query client and no global state — it does not compete\n * with the host's Redux or React Query, it composes under either. What it owns\n * is the part every copy gets wrong: cancellation, stale-response ordering,\n * honest error conversion, retry classification, reconnection, and\n * double-submit refusal.\n *\n * Every entry here is stamped `\"use client\"`.\n */\n\nexport {\n useAsyncData,\n type AsyncData,\n type AsyncDataContext,\n type UseAsyncDataOptions,\n} from \"./react/use-async-data\";\n\nexport {\n useRealtimeChannel,\n type PostgresChangeBinding,\n type PostgresChangeEvent,\n type PostgresChangeFilter,\n type PostgresChangePayload,\n type RealtimeChannelLike,\n type RealtimeChannelState,\n type RealtimeClientLike,\n type RealtimeStatus,\n type UseRealtimeChannelOptions,\n} from \"./react/use-realtime-channel\";\n\nexport {\n useRealtimeQuery,\n type RealtimeQuery,\n type UseRealtimeQueryOptions,\n type WatchedTable,\n} from \"./react/use-realtime-query\";\n\nexport {\n ActionInFlightError,\n useAsyncAction,\n type ActionResult,\n type AsyncAction,\n type UseAsyncActionOptions,\n} from \"./react/use-async-action\";\n\nexport {\n usePaginatedData,\n type PaginatedData,\n type PaginatedDataOptions,\n type PaginatedPage,\n} from \"./react/use-paginated-data\";\n\nexport {\n resolveScrollPaginationPolicy,\n suspendScrollPaginationPolicy,\n type ScrollPaginationPolicy,\n} from \"./pagination/scroll-policy\";\n","\"use client\";\n\n/**\n * `useAsyncData` — the one honest way to read data into a React component.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 93 files hand-wire the exact\n * same five pieces — a `cancelled` flag, a `loading` boolean, an `error`\n * string, a refetch tick, and a `.catch()`. Each copy re-derives the same\n * mistakes, and the census found them all in the wild:\n *\n * 1. `setError(String(err))` — a PostgREST error is a PLAIN OBJECT, not an\n * `Error`, so `String(err)` renders \"[object Object]\" to the user and the\n * real `message`/`code`/`hint` are lost. This hook runs every failure\n * through `pgErrorToError`.\n * 2. No `AbortSignal` reaches the loader, so a superseded request keeps\n * running to completion, burning a connection and (worse) still counting\n * against rate limits. This hook aborts the previous run.\n * 3. Stale-response ordering — nothing stops a slow first request from\n * overwriting a fast second one. This hook stamps every run and ignores\n * any result that is not the newest.\n * 4. `loading` flips true on every key change, blanking the screen even when\n * previous data is still perfectly displayable. `keepPreviousData` is ON\n * by default and `refreshing` reports the background load honestly.\n * 5. An abort is treated as a failure and painted as an error banner. Here an\n * abort is silent by construction — it is not a failure, it is a\n * cancellation the component itself asked for.\n * 6. No retry, so one transient network blip is a permanent empty state.\n * Transient TRANSPORT failures retry with backoff; a server that answered\n * (a 400, an RLS denial) never does — retrying those is how you turn one\n * bug into a storm.\n *\n * Nothing fails silently: a terminal failure always lands in `error`, and\n * `onError` fires with the same real `Error`.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\nimport { isNetError, toNetError } from \"../net/errors\";\n\nexport interface UseAsyncDataOptions<T> {\n /**\n * Skip loading entirely (e.g. the key is null). Default `true`.\n * While disabled: `loading` is false and existing data is retained.\n */\n enabled?: boolean;\n /** Shown before the first load resolves. */\n initialData?: T;\n /**\n * Keep the previous key's data visible while the new key loads.\n * Default `true` — a list that blanks on every filter change is a worse\n * experience than a list that is briefly one keystroke stale.\n */\n keepPreviousData?: boolean;\n /**\n * How many times to retry a TRANSIENT TRANSPORT failure. Default `2`.\n * Errors the server actually answered with are never retried.\n */\n retry?: number;\n /** First backoff delay in ms; doubles per attempt. Default `300`. */\n retryDelayMs?: number;\n /** Called once per terminal failure, with the real `Error`. */\n onError?: (error: Error) => void;\n}\n\nexport interface AsyncData<T> {\n /** `undefined` until the first load resolves (or `initialData`). */\n data: T | undefined;\n /** True only while there is nothing to show yet. */\n loading: boolean;\n /** True while a load runs over data that is already on screen. */\n refreshing: boolean;\n /** The last terminal failure, as a real `Error`. Cleared on success. */\n error: Error | null;\n /** Re-run the loader now. Safe to call from an event handler or effect. */\n refresh: () => void;\n}\n\nexport interface AsyncDataContext {\n signal: AbortSignal;\n}\n\nfunction isAbort(error: unknown): boolean {\n if (error instanceof DOMException && error.name === \"AbortError\") return true;\n if (error instanceof Error && error.name === \"AbortError\") return true;\n return false;\n}\n\n/** Only a transport that never got an answer is worth retrying. */\nfunction isRetryable(error: unknown): boolean {\n const net = isNetError(error) ? error : toNetError(error);\n return net.retryable;\n}\n\nconst sleep = (ms: number, signal: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n function onAbort(): void {\n clearTimeout(timer);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n\nexport function useAsyncData<T>(\n load: (context: AsyncDataContext) => Promise<T>,\n deps: readonly unknown[],\n options: UseAsyncDataOptions<T> = {},\n): AsyncData<T> {\n const {\n enabled = true,\n initialData,\n keepPreviousData = true,\n retry = 2,\n retryDelayMs = 300,\n onError,\n } = options;\n\n const [data, setData] = useState<T | undefined>(initialData);\n const [loading, setLoading] = useState<boolean>(enabled);\n const [refreshing, setRefreshing] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n const [tick, setTick] = useState(0);\n\n // Latest-value refs so a changed callback identity never re-triggers a load\n // (the host runs the React Compiler; the package must be correct without it).\n const loadRef = useRef(load);\n loadRef.current = load;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const runIdRef = useRef(0);\n const hasDataRef = useRef(initialData !== undefined);\n\n const refresh = useCallback(() => setTick((n) => n + 1), []);\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n setRefreshing(false);\n return undefined;\n }\n\n const runId = ++runIdRef.current;\n const controller = new AbortController();\n const showingData = hasDataRef.current && keepPreviousData;\n\n if (!keepPreviousData) {\n hasDataRef.current = false;\n setData(undefined);\n }\n setLoading(!showingData);\n setRefreshing(showingData);\n\n void (async () => {\n let attempt = 0;\n for (;;) {\n try {\n const result = await loadRef.current({ signal: controller.signal });\n if (runId !== runIdRef.current || controller.signal.aborted) return;\n hasDataRef.current = true;\n setData(result);\n setError(null);\n setLoading(false);\n setRefreshing(false);\n return;\n } catch (raw) {\n if (isAbort(raw) || controller.signal.aborted) return;\n if (runId !== runIdRef.current) return;\n if (attempt < retry && isRetryable(raw)) {\n attempt += 1;\n try {\n await sleep(retryDelayMs * 2 ** (attempt - 1), controller.signal);\n } catch {\n return;\n }\n continue;\n }\n const failure = pgErrorToError(raw);\n setError(failure);\n setLoading(false);\n setRefreshing(false);\n onErrorRef.current?.(failure);\n return;\n }\n }\n })();\n\n return () => {\n controller.abort();\n };\n // `deps` is the caller's declared key set — the whole point of the hook.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, keepPreviousData, retry, retryDelayMs, tick, ...deps]);\n\n return { data, loading, refreshing, error, refresh };\n}\n","/**\n * Structured errors for the resilience layer.\n *\n * Every network-layer failure resolves to one of these classes so callers\n * (and any recovery UI) can branch on error type, not on string matching.\n *\n * Ported verbatim from matrx-frontend `lib/net/errors.ts` — the origin of the\n * trio. The one coupling seam removed: `extractErrorMessage` (previously the\n * app's `utils/errors`) is pure logic and ships inside the package.\n */\n\nexport type NetErrorCode =\n | \"connect-timeout\"\n | \"total-timeout\"\n | \"heartbeat-timeout\"\n | \"network\"\n | \"http\"\n | \"aborted\"\n | \"offline\"\n | \"unknown\";\n\nexport class NetError extends Error {\n readonly code: NetErrorCode;\n override readonly cause?: unknown;\n readonly status?: number;\n readonly retryable: boolean;\n\n constructor(\n code: NetErrorCode,\n message: string,\n opts: { cause?: unknown; status?: number; retryable?: boolean } = {},\n ) {\n super(message);\n this.name = \"NetError\";\n this.code = code;\n this.cause = opts.cause;\n // Conditional only for `exactOptionalPropertyTypes` — same runtime shape.\n if (opts.status !== undefined) this.status = opts.status;\n this.retryable = opts.retryable ?? false;\n }\n}\n\nexport class ConnectTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\"connect-timeout\", `Connection timed out after ${ms}ms`, {\n cause,\n retryable: true,\n });\n this.name = \"ConnectTimeoutError\";\n }\n}\n\nexport class TotalTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\"total-timeout\", `Request exceeded ${ms}ms total budget`, {\n cause,\n retryable: false,\n });\n this.name = \"TotalTimeoutError\";\n }\n}\n\nexport class HeartbeatTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\n \"heartbeat-timeout\",\n `No server activity for ${ms}ms — stream considered dead`,\n { cause, retryable: false },\n );\n this.name = \"HeartbeatTimeoutError\";\n }\n}\n\nexport class NetworkError extends NetError {\n constructor(message: string, cause?: unknown) {\n super(\"network\", message, { cause, retryable: true });\n this.name = \"NetworkError\";\n }\n}\n\nexport class HttpError extends NetError {\n constructor(status: number, message: string, cause?: unknown) {\n // 408/429/5xx are generally safe to retry for idempotent ops.\n const retryable = status === 408 || status === 429 || status >= 500;\n super(\"http\", message, { cause, status, retryable });\n this.name = \"HttpError\";\n }\n}\n\nexport class AbortedError extends NetError {\n constructor(cause?: unknown) {\n super(\"aborted\", \"Request was aborted\", { cause, retryable: false });\n this.name = \"AbortedError\";\n }\n}\n\nexport class OfflineError extends NetError {\n constructor() {\n super(\"offline\", \"Browser is offline\", { retryable: true });\n this.name = \"OfflineError\";\n }\n}\n\nexport function isNetError(err: unknown): err is NetError {\n return err instanceof NetError;\n}\n\n/**\n * Folds any thrown value into a descriptive message string — THE one reader\n * for every failure shape this platform actually emits.\n *\n * 2026-09-07 (duplication census rows 9, 10, 21): this body had DRIFTED BEHIND\n * three host copies, which is the campaign's failure mode running in the\n * package's favour-direction. Each host had learned something the package\n * never did, so every non-frontend consumer of `@ai-matrx/data/net` was\n * flattening a server's own sentence into \"An unexpected error occurred\". All\n * three are folded in here, with a named regression test per shape:\n *\n * 1. **FastAPI's `detail`** (matrx-frontend `utils/errors.ts`, learned\n * 2026-08-31 as V2 finding G5). Our server refuses in `detail` — a string,\n * or an object carrying `message` — and every reader checked\n * `message`/`details` only. The server's sentence is the whole point of\n * catching this.\n * 2. **A `fallback` argument** (matrx-frontend `ShortcutForm.tsx`). A caller\n * who knows what it was doing can say \"Failed to save shortcut\" instead of\n * the generic sentence. Optional; the default is unchanged, so every\n * existing single-argument call behaves exactly as before.\n * 3. **Recursion into nested error objects** (aidream `workflow-studio`, two\n * byte-identical copies). The run pipeline nests: `{error: {type, message}}`\n * and `{error: {error: ...}}` and `{exception: {...}}` are all real shapes,\n * and a non-recursive reader answers \"An unexpected error occurred\" while\n * holding the actual reason two levels down. Depth-capped at 4 — a cyclic\n * or absurdly deep object must not hang a render.\n *\n * PostgREST-style errors are plain objects carrying\n * `message`/`details`/`hint`/`code`; a bare `String(err)` on those yields\n * \"[object Object]\", which is why this exists at all.\n */\nexport function extractErrorMessage(\n err: unknown,\n fallback = \"An unexpected error occurred\",\n): string {\n if (!err) return \"Unknown error\";\n if (typeof err === \"string\") return err;\n if (err instanceof Error) return err.message;\n if (typeof err === \"object\") {\n const e = err as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof e.message === \"string\" && e.message) parts.push(e.message);\n // FastAPI's own word for the reason — only when nothing said `message`,\n // so a PostgREST error keeps its exact composed sentence.\n if (parts.length === 0) {\n if (typeof e.detail === \"string\" && e.detail) parts.push(e.detail);\n else if (e.detail && typeof e.detail === \"object\") {\n const d = e.detail as Record<string, unknown>;\n if (typeof d.message === \"string\" && d.message) parts.push(d.message);\n }\n }\n if (typeof e.details === \"string\" && e.details) parts.push(e.details);\n if (typeof e.hint === \"string\" && e.hint) parts.push(`Hint: ${e.hint}`);\n if (typeof e.code === \"string\" && e.code) parts.push(`Code: ${e.code}`);\n if (parts.length > 0) return parts.join(\" · \");\n\n const nested = extractNestedErrorMessage(e, 0);\n if (nested !== null) return nested;\n\n try {\n const s = JSON.stringify(err);\n if (s && s !== \"{}\") return s;\n } catch {\n /* ignore */\n }\n }\n return fallback;\n}\n\n/** Depth-capped walk into `error` / `exception` / `cause`. See above. */\nfunction extractNestedErrorMessage(\n e: Record<string, unknown>,\n depth: number,\n): string | null {\n if (depth > 4) return null;\n for (const key of [\"error\", \"exception\", \"cause\"] as const) {\n const value = e[key];\n if (typeof value === \"string\" && value) return value;\n if (value && typeof value === \"object\") {\n const inner = value as Record<string, unknown>;\n if (typeof inner.message === \"string\" && inner.message) return inner.message;\n if (typeof inner.detail === \"string\" && inner.detail) return inner.detail;\n const deeper = extractNestedErrorMessage(inner, depth + 1);\n if (deeper !== null) return deeper;\n }\n }\n return null;\n}\n\n/**\n * True when a failure is the TRANSPORT dying, not the server answering: the\n * browser is offline, asleep, mid-wifi-handoff, or DNS/TLS failed. It also\n * covers the bounded Envoy/Supabase gateway shape emitted when the upstream\n * database connection dies before response headers. `fetch`\n * rejects with a bare `TypeError: Failed to fetch` in every browser, and\n * supabase-js surfaces that verbatim (message only — no code, no hint).\n *\n * Use it to decide how LOUD a failure is, never whether to handle it. A\n * transport failure carries nothing an engineer can act on, so logging it at\n * error level buries the failures that do — that is how a notes surface filed\n * a system_error every time a laptop slept (2026-08-11). Anything with a\n * Postgres code, an HTTP status, or a server message is NOT this: keep those\n * loud.\n */\nexport function isTransportFailure(err: unknown): boolean {\n if (err instanceof NetworkError || err instanceof OfflineError) return true;\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n if (err instanceof TypeError) return true;\n if (err && typeof err === \"object\") {\n const e = err as { code?: unknown; status?: unknown; message?: unknown; name?: unknown };\n // A real PostgREST/Postgres failure always carries a code or status.\n if (e.code || e.status) return false;\n const text = `${typeof e.name === \"string\" ? e.name : \"\"} ${\n typeof e.message === \"string\" ? e.message : \"\"\n }`.toLowerCase();\n return (\n text.includes(\"failed to fetch\") ||\n text.includes(\"networkerror\") ||\n text.includes(\"network request failed\") ||\n text.includes(\"load failed\") || // Safari's wording for the same condition\n text.includes(\"upstream connect error or disconnect/reset before headers\")\n );\n }\n return false;\n}\n\nexport function toNetError(err: unknown): NetError {\n if (isNetError(err)) return err;\n if (err instanceof DOMException && err.name === \"AbortError\") {\n return new AbortedError(err);\n }\n if (err instanceof TypeError) {\n // fetch() throws TypeError on DNS/connection failure in every browser.\n return new NetworkError(err.message || \"Network request failed\", err);\n }\n return new NetError(\n \"unknown\",\n extractErrorMessage(err),\n { cause: err },\n );\n}\n","/**\n * @ai-matrx/data — the core module.\n *\n * The shared structural vocabulary every lane of the data layer speaks, plus\n * the lane-free net-resilience trio (S12 rides the core — re-exported from\n * `./net`, also importable as `@ai-matrx/data/net`). This entry point\n * otherwise deliberately stays tiny (the split-out law): the Supabase lane is\n * `@ai-matrx/data/db`; the API and files lanes arrive in later minors. Nothing\n * here performs network, storage, or global-state work at import time.\n *\n * The types are STRUCTURAL on purpose: the package never imports\n * `@supabase/supabase-js` — a real `PostgrestError` satisfies\n * `PostgrestLikeError` by shape, and so does any compatible client, so the\n * package works against whatever client the host injects.\n */\n\n/** The shape of a PostgREST error object (plain object, NOT an Error). */\nexport interface PostgrestLikeError {\n message: string;\n details?: string | null;\n hint?: string | null;\n code?: string | null;\n}\n\n/** The shape a `.maybeSingle()` call resolves to. */\nexport interface MaybeSingleResponse<Row> {\n data: Row | null;\n error: PostgrestLikeError | null;\n}\n\n/** A JSON value — the wire-safe subset every column and payload speaks. */\nexport type Json =\n | string\n | number\n | boolean\n | null\n | { [key: string]: Json | undefined }\n | Json[];\n\n/**\n * Converts any thrown value from a Supabase/PostgREST call into a proper Error\n * with a descriptive message. PostgREST errors are plain objects with `message`,\n * `code`, `details`, and `hint` — they are NOT `instanceof Error`, which causes\n * generic catch blocks to lose the real reason.\n */\nexport function pgErrorToError(err: unknown): Error {\n if (err instanceof Error) return err;\n if (err && typeof err === \"object\") {\n const e = err as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof e.message === \"string\" && e.message) parts.push(e.message);\n if (typeof e.details === \"string\" && e.details)\n parts.push(`Details: ${e.details}`);\n if (typeof e.hint === \"string\" && e.hint) parts.push(`Hint: ${e.hint}`);\n if (typeof e.code === \"string\" && e.code) parts.push(`(code: ${e.code})`);\n if (parts.length > 0) return new Error(parts.join(\" — \"));\n }\n return new Error(String(err));\n}\n\nexport * from \"./credentials\";\nexport * from \"./net\";\n","\"use client\";\n\n/**\n * `useRealtimeChannel` — a Supabase Realtime subscription that stays alive.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 18 React modules open a\n * `postgres_changes` channel by hand. Only ONE of them reconnects. The rest\n * share a defect class that is invisible in testing and brutal in production:\n * a channel that drops (laptop sleep, a proxy idling the socket, a Realtime\n * deploy) never comes back, so the screen quietly stops updating and nothing\n * anywhere says so. The user believes stale data.\n *\n * Every hard part lives here:\n *\n * - **Unique topic per mount** (`uniqueChannelTopic`) — React 19\n * double-invokes effects and Fast Refresh re-runs them; a shared topic\n * collides with a still-joined channel and the second subscribe never\n * fires.\n * - **Reconnect with exponential backoff and a stability reset** — a channel\n * that stayed up for a while starts its next backoff from the bottom\n * instead of inheriting an hour-old delay.\n * - **Catch-up on reconnect** (`onReconnect`) — the events that happened\n * while the socket was down are GONE. Reconnecting without a re-read\n * leaves a permanently wrong screen; this hook makes the re-read part of\n * reconnecting rather than something each caller remembers.\n * - **Honest status** — `connecting | connected | reconnecting | disconnected`,\n * so a surface can tell the user it is stale instead of lying silently.\n * - **Latest-callback refs** — a new inline handler on every render must not\n * tear down and rebuild the subscription (the \"why does my realtime\n * reconnect forever\" bug).\n * - **Guaranteed teardown**, including for a channel that is still mid-join.\n *\n * The client type is STRUCTURAL: this package never imports\n * `@supabase/supabase-js`. A real Supabase client satisfies it by shape (there\n * is a compile-time proof of that in `use-realtime-channel.test.tsx`).\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { uniqueChannelTopic } from \"../db/realtime\";\n\nexport type RealtimeStatus =\n | \"connecting\"\n | \"connected\"\n | \"reconnecting\"\n | \"disconnected\";\n\nexport type PostgresChangeEvent = \"*\" | \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\n/** The payload shape `postgres_changes` delivers. */\nexport interface PostgresChangePayload {\n eventType?: string;\n new?: unknown;\n old?: unknown;\n}\n\nexport interface PostgresChangeBinding {\n event: PostgresChangeEvent;\n schema: string;\n table: string;\n /** PostgREST filter, e.g. `file_id=eq.<uuid>`. */\n filter?: string;\n onChange: (payload: PostgresChangePayload) => void;\n}\n\n/** The `postgres_changes` filter, exactly as Supabase declares it. */\nexport interface PostgresChangeFilter {\n event: PostgresChangeEvent;\n schema: string;\n table?: string;\n filter?: string;\n}\n\n/**\n * Structural stand-in for `RealtimeChannel`. The signature must match\n * Supabase's `postgres_changes` overload PRECISELY — a wider `filter` type or\n * a `never`-typed callback makes the real client fail to satisfy this\n * interface, and the host discovers it at its own typecheck. That is exactly\n * what the compile-time proof in `use-realtime-channel.test.tsx` caught during\n * this package's own build.\n */\nexport interface RealtimeChannelLike {\n on(\n type: \"postgres_changes\",\n filter: PostgresChangeFilter,\n callback: (payload: PostgresChangePayload) => void,\n ): RealtimeChannelLike;\n subscribe(callback?: (status: string, error?: Error) => void): unknown;\n}\n\n/** Structural stand-in for `SupabaseClient` (realtime surface only). */\nexport interface RealtimeClientLike {\n channel(topic: string): RealtimeChannelLike;\n removeChannel(channel: RealtimeChannelLike): unknown;\n}\n\nexport interface UseRealtimeChannelOptions {\n /** Turn the subscription off without unmounting. Default `true`. */\n enabled?: boolean;\n /**\n * Called after a DROPPED channel comes back. This is the catch-up door:\n * re-read whatever the missed events would have changed.\n */\n onReconnect?: () => void;\n /** First reconnect delay in ms; doubles up to `maxReconnectDelayMs`. Default `1000`. */\n reconnectDelayMs?: number;\n /** Ceiling for the reconnect backoff. Default `30000`. */\n maxReconnectDelayMs?: number;\n /**\n * How long a channel must stay connected before its backoff resets to the\n * floor. Default `30000` — without it a flapping connection inherits an\n * ever-growing delay and effectively stops trying.\n */\n backoffResetAfterMs?: number;\n}\n\nexport interface RealtimeChannelState {\n status: RealtimeStatus;\n /** True once the channel has dropped at least once this mount. */\n hasReconnected: boolean;\n}\n\nexport function useRealtimeChannel(\n client: RealtimeClientLike | null | undefined,\n topic: string,\n bindings: readonly PostgresChangeBinding[],\n options: UseRealtimeChannelOptions = {},\n): RealtimeChannelState {\n const {\n enabled = true,\n onReconnect,\n reconnectDelayMs = 1_000,\n maxReconnectDelayMs = 30_000,\n backoffResetAfterMs = 30_000,\n } = options;\n\n const [status, setStatus] = useState<RealtimeStatus>(\n enabled && client ? \"connecting\" : \"disconnected\",\n );\n const [hasReconnected, setHasReconnected] = useState(false);\n\n const bindingsRef = useRef(bindings);\n bindingsRef.current = bindings;\n const onReconnectRef = useRef(onReconnect);\n onReconnectRef.current = onReconnect;\n\n // The binding SHAPE (not the handler identities) is what must re-subscribe.\n const shape = bindings\n .map((b) => `${b.event}:${b.schema}.${b.table}:${b.filter ?? \"\"}`)\n .join(\"|\");\n\n useEffect(() => {\n if (!client || !enabled) {\n setStatus(\"disconnected\");\n return undefined;\n }\n\n let disposed = false;\n let attempt = 0;\n let everConnected = false;\n let connectedAt = 0;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let channel: RealtimeChannelLike | null = null;\n\n const teardown = (): void => {\n if (!channel) return;\n const dead = channel;\n channel = null;\n try {\n void client.removeChannel(dead);\n } catch {\n // A channel torn down mid-join can reject; the socket drops it anyway\n // and there is nothing a caller could do about it.\n }\n };\n\n const scheduleReconnect = (): void => {\n if (disposed) return;\n // A channel that held for a while earned a fresh backoff floor.\n if (everConnected && Date.now() - connectedAt >= backoffResetAfterMs) {\n attempt = 0;\n }\n const delay = Math.min(\n reconnectDelayMs * 2 ** attempt,\n maxReconnectDelayMs,\n );\n attempt += 1;\n setStatus(everConnected ? \"reconnecting\" : \"connecting\");\n retryTimer = setTimeout(() => {\n teardown();\n connect();\n }, delay);\n };\n\n const connect = (): void => {\n if (disposed) return;\n let next = client.channel(uniqueChannelTopic(topic));\n for (const [index, binding] of bindingsRef.current.entries()) {\n next = next.on(\n \"postgres_changes\",\n {\n event: binding.event,\n schema: binding.schema,\n table: binding.table,\n ...(binding.filter !== undefined ? { filter: binding.filter } : {}),\n },\n (payload: PostgresChangePayload) => {\n // Read through the ref so a re-rendered inline handler is used\n // without rebuilding the subscription.\n bindingsRef.current[index]?.onChange(payload);\n },\n );\n }\n channel = next;\n next.subscribe((subscribeStatus) => {\n if (disposed) return;\n if (subscribeStatus === \"SUBSCRIBED\") {\n const rejoined = everConnected;\n everConnected = true;\n connectedAt = Date.now();\n attempt = 0;\n setStatus(\"connected\");\n if (rejoined) {\n setHasReconnected(true);\n onReconnectRef.current?.();\n }\n return;\n }\n if (\n subscribeStatus === \"CHANNEL_ERROR\" ||\n subscribeStatus === \"TIMED_OUT\" ||\n subscribeStatus === \"CLOSED\"\n ) {\n scheduleReconnect();\n }\n });\n };\n\n connect();\n\n return () => {\n disposed = true;\n if (retryTimer !== undefined) clearTimeout(retryTimer);\n teardown();\n };\n }, [\n client,\n enabled,\n topic,\n shape,\n reconnectDelayMs,\n maxReconnectDelayMs,\n backoffResetAfterMs,\n ]);\n\n return { status, hasReconnected };\n}\n","/**\n * Realtime channel hygiene — RE-EXPORT ONLY. The canonical home is\n * `@ai-matrx/realtime`.\n *\n * Channel NAMING belongs to the realtime package (its work order, R1): it owns\n * the channel-naming registry, and `uniqueChannelTopic` is the React-19\n * double-invoked-effect collision guard that registry hands out. This package\n * kept the only copy while `@ai-matrx/realtime` was unpublished; that copy is\n * now DELETED and this module forwards, so the two can never drift and the\n * `#`-suffixed instance topics a Matrx app opens all come from one counter.\n *\n * Why forwarding matters and a second copy would not do: the old\n * implementation kept its sequence in a module-level `let`. With unsplit dual\n * ESM/CJS output a module can be instantiated twice in one process, so two\n * counters both starting at 1 hand out the same \"unique\" topic twice — exactly\n * the collision the function exists to prevent. The realtime package keeps the\n * counter in a `globalThis` slot (`Symbol.for`) and proves it across module\n * graphs in its packed-tarball canary.\n *\n * `@ai-matrx/realtime` is the deeper layer (zero runtime dependencies; it does\n * not and must not depend on `@ai-matrx/data`), so this direction keeps the\n * sibling graph acyclic — `node scripts/check_ts_sibling_graph.mjs`.\n *\n * New realtime work should not reach for this at all: declare a channel\n * namespace with `defineChannelNamespace` and let the manager assign the\n * instance topic. This export exists for the call sites that have not yet\n * migrated off hand-rolled `.channel(` blocks.\n */\n\nexport { uniqueChannelTopic } from \"@ai-matrx/realtime\";\n","\"use client\";\n\n/**\n * `useRealtimeQuery` — a read that stays current, in one call.\n *\n * This is the exact shape 8+ matrx-frontend hooks hand-assemble: load a list,\n * subscribe to the rows behind it, refresh on change. Composing the two\n * primitives here (rather than in each host hook) closes the gap none of the\n * hand-rolled copies close — a change that arrives while the socket is down is\n * never delivered, so reconnecting MUST re-read. `useRealtimeChannel`'s\n * `onReconnect` is wired to `refresh` for you.\n *\n * Realtime events are COALESCED: a bulk write firing 200 INSERTs must not\n * produce 200 reads. Changes inside `debounceMs` collapse into one refresh.\n */\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport {\n useAsyncData,\n type AsyncData,\n type AsyncDataContext,\n type UseAsyncDataOptions,\n} from \"./use-async-data\";\nimport {\n useRealtimeChannel,\n type PostgresChangeBinding,\n type PostgresChangeEvent,\n type RealtimeClientLike,\n type RealtimeStatus,\n type UseRealtimeChannelOptions,\n} from \"./use-realtime-channel\";\n\n/** A table to watch. `onChange` is optional — refreshing is the default. */\nexport interface WatchedTable {\n event?: PostgresChangeEvent;\n schema: string;\n table: string;\n filter?: string;\n}\n\nexport interface UseRealtimeQueryOptions<T>\n extends UseAsyncDataOptions<T>,\n Pick<\n UseRealtimeChannelOptions,\n \"reconnectDelayMs\" | \"maxReconnectDelayMs\" | \"backoffResetAfterMs\"\n > {\n /** Coalesce bursts of realtime events into one refresh. Default `120`ms. */\n debounceMs?: number;\n}\n\nexport interface RealtimeQuery<T> extends AsyncData<T> {\n realtimeStatus: RealtimeStatus;\n}\n\nexport function useRealtimeQuery<T>(\n args: {\n client: RealtimeClientLike | null | undefined;\n topic: string;\n watch: readonly WatchedTable[];\n load: (context: AsyncDataContext) => Promise<T>;\n deps: readonly unknown[];\n },\n options: UseRealtimeQueryOptions<T> = {},\n): RealtimeQuery<T> {\n const {\n debounceMs = 120,\n reconnectDelayMs,\n maxReconnectDelayMs,\n backoffResetAfterMs,\n ...dataOptions\n } = options;\n const query = useAsyncData(args.load, args.deps, dataOptions);\n\n const refreshRef = useRef(query.refresh);\n refreshRef.current = query.refresh;\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(\n () => () => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n },\n [],\n );\n\n const shape = args.watch\n .map((w) => `${w.event ?? \"*\"}:${w.schema}.${w.table}:${w.filter ?? \"\"}`)\n .join(\"|\");\n\n const bindings = useMemo<PostgresChangeBinding[]>(() => {\n const coalescedRefresh = (): void => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => {\n timerRef.current = undefined;\n refreshRef.current();\n }, debounceMs);\n };\n return args.watch.map((w) => ({\n event: w.event ?? \"*\",\n schema: w.schema,\n table: w.table,\n ...(w.filter !== undefined ? { filter: w.filter } : {}),\n onChange: coalescedRefresh,\n }));\n // `shape` is the stable identity of `args.watch`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shape, debounceMs]);\n\n const { status } = useRealtimeChannel(args.client, args.topic, bindings, {\n enabled: dataOptions.enabled ?? true,\n ...(reconnectDelayMs !== undefined ? { reconnectDelayMs } : {}),\n ...(maxReconnectDelayMs !== undefined ? { maxReconnectDelayMs } : {}),\n ...(backoffResetAfterMs !== undefined ? { backoffResetAfterMs } : {}),\n // The events lost while the socket was down are unrecoverable; the read is\n // the only way back to truth.\n onReconnect: () => refreshRef.current(),\n });\n\n return { ...query, realtimeStatus: status };\n}\n","\"use client\";\n\n/**\n * `useAsyncAction` — a write with honest pending/error state.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 554 components hold their own\n * `saving`/`submitting`/`deleting` boolean around an async call. The repeated\n * defects:\n *\n * 1. **No double-submit guard.** A disabled button is not one — a fast second\n * click, an Enter key, or a re-render race all get through, and the write\n * runs twice. Here a second `run` while one is in flight is REFUSED and\n * says so (`{ ok: false, error: ActionInFlightError }`) instead of quietly\n * duplicating a row.\n * 2. **`setState` after unmount**, because `finally { setSaving(false) }`\n * does not know the component is gone.\n * 3. **`String(err)` again** — a PostgREST error object renders as\n * \"[object Object]\". Every failure here goes through `pgErrorToError`.\n * 4. **The error is thrown AND stored, or neither.** This hook returns a\n * discriminated result so a caller can branch without try/catch, while\n * `error` holds the same `Error` for rendering. Nothing is swallowed:\n * the failure is in the return value, in `error`, and in `onError`.\n *\n * Writes are NEVER retried here — that is the whole point of the boundary. A\n * user-authored submission replayed after an ambiguous timeout is how you get\n * two invoices. Retry belongs on idempotent reads (`useAsyncData`).\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\n\n/** Thrown-shaped refusal when a second run overlaps an in-flight one. */\nexport class ActionInFlightError extends Error {\n constructor() {\n super(\n \"This action is already running. The duplicate submission was refused — \" +\n \"wait for the in-flight action to finish.\",\n );\n this.name = \"ActionInFlightError\";\n }\n}\n\nexport type ActionResult<R> =\n | { ok: true; value: R }\n | { ok: false; error: Error };\n\nexport interface UseAsyncActionOptions<R> {\n onSuccess?: (value: R) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface AsyncAction<Args extends readonly unknown[], R> {\n run: (...args: Args) => Promise<ActionResult<R>>;\n pending: boolean;\n /** The last failure, as a real `Error`. Cleared when a new run starts. */\n error: Error | null;\n /** Clear `error` without running anything. */\n reset: () => void;\n}\n\nexport function useAsyncAction<Args extends readonly unknown[], R>(\n action: (...args: Args) => Promise<R>,\n options: UseAsyncActionOptions<R> = {},\n): AsyncAction<Args, R> {\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const actionRef = useRef(action);\n actionRef.current = action;\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const inFlightRef = useRef(false);\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const run = useCallback(async (...args: Args): Promise<ActionResult<R>> => {\n if (inFlightRef.current) {\n const refusal = new ActionInFlightError();\n if (mountedRef.current) setError(refusal);\n optionsRef.current.onError?.(refusal);\n return { ok: false, error: refusal };\n }\n inFlightRef.current = true;\n if (mountedRef.current) {\n setPending(true);\n setError(null);\n }\n try {\n const value = await actionRef.current(...args);\n if (mountedRef.current) setPending(false);\n optionsRef.current.onSuccess?.(value);\n return { ok: true, value };\n } catch (raw) {\n const failure = pgErrorToError(raw);\n if (mountedRef.current) {\n setPending(false);\n setError(failure);\n }\n optionsRef.current.onError?.(failure);\n return { ok: false, error: failure };\n } finally {\n inFlightRef.current = false;\n }\n }, []);\n\n const reset = useCallback(() => setError(null), []);\n\n return { run, pending, error, reset };\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\n\nexport interface PaginatedPage<T, C extends string | number> {\n rows: T[];\n nextCursor: C | null;\n totalItems?: number;\n}\n\nexport interface PaginatedDataOptions<T, C extends string | number> {\n /** Immutable identity of every input that changes the source result. */\n queryKey: string;\n initialCursor: C;\n loadPage: (context: { cursor: C; signal: AbortSignal }) => Promise<PaginatedPage<T, C>>;\n getRowId: (row: T) => string;\n enabled?: boolean;\n onError?: (error: Error) => void;\n}\n\nexport interface PaginatedData<T> {\n queryKey: string;\n rows: T[];\n loading: boolean;\n isFetchingNextPage: boolean;\n error: Error | null;\n hasNextPage: boolean;\n loadNextPage: () => Promise<void>;\n refresh: () => void;\n totalItems: number | undefined;\n isLoadingAll: boolean;\n requestLoadAll: () => void;\n stopLoadingAll: () => void;\n}\n\ntype Phase = \"idle\" | \"initial\" | \"ready\" | \"next\" | \"initial-error\" | \"next-error\" | \"cursor-error\";\n\ninterface Snapshot<T, C extends string | number> {\n queryKey: string | null;\n initialCursor: C | null;\n rows: T[];\n nextCursor: C | null;\n totalItems: number | undefined;\n error: Error | null;\n phase: Phase;\n}\n\nfunction isAbort(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === \"AbortError\";\n}\n\nfunction mergeRows<T>(current: readonly T[], incoming: readonly T[], getRowId: (row: T) => string): T[] {\n const next = [...current];\n const indexById = new Map<string, number>();\n current.forEach((row, index) => indexById.set(getRowId(row), index));\n for (const row of incoming) {\n const id = getRowId(row);\n const existing = indexById.get(id);\n if (existing === undefined) {\n indexById.set(id, next.length);\n next.push(row);\n } else {\n // The server's newer representation wins without moving visible rows.\n next[existing] = row;\n }\n }\n return next;\n}\n\n/** Cursor accumulation with no cache and no table dependency. */\nexport function usePaginatedData<T, C extends string | number>(\n options: PaginatedDataOptions<T, C>,\n): PaginatedData<T> {\n const { queryKey, initialCursor, enabled = true } = options;\n const [refreshTick, setRefreshTick] = useState(0);\n const [loadAllRequested, setLoadAllRequested] = useState(false);\n const [snapshot, setSnapshot] = useState<Snapshot<T, C>>({\n queryKey: null, initialCursor: null, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"idle\",\n });\n const snapshotRef = useRef(snapshot);\n const queryKeyRef = useRef(queryKey);\n queryKeyRef.current = queryKey;\n const initialCursorRef = useRef(initialCursor);\n initialCursorRef.current = initialCursor;\n const enabledRef = useRef(enabled);\n enabledRef.current = enabled;\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const generationRef = useRef(0);\n const inFlightRef = useRef(false);\n const controllerRef = useRef<AbortController | null>(null);\n const receiptsRef = useRef(new Set<C>());\n\n const publish = useCallback((next: Snapshot<T, C>) => {\n snapshotRef.current = next;\n setSnapshot(next);\n }, []);\n const isCurrent = useCallback((requestKey: string, requestInitialCursor: C, generation: number, signal: AbortSignal) => (\n !signal.aborted && generation === generationRef.current && requestKey === queryKeyRef.current && requestInitialCursor === initialCursorRef.current && enabledRef.current\n ), []);\n\n const requestPage = useCallback(async (\n cursor: C, kind: \"initial\" | \"next\", requestKey: string, requestInitialCursor: C, generation: number,\n ): Promise<void> => {\n if (inFlightRef.current || !enabledRef.current || requestKey !== queryKeyRef.current) return;\n const controller = new AbortController();\n controllerRef.current = controller;\n inFlightRef.current = true;\n publish({ ...snapshotRef.current, queryKey: requestKey, initialCursor: requestInitialCursor, error: null, phase: kind });\n try {\n const page = await optionsRef.current.loadPage({ cursor, signal: controller.signal });\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n const rows = mergeRows(\n kind === \"initial\" ? [] : snapshotRef.current.rows,\n page.rows,\n optionsRef.current.getRowId,\n );\n receiptsRef.current.add(cursor);\n const repeatedCursor = page.nextCursor !== null && receiptsRef.current.has(page.nextCursor);\n const error = repeatedCursor\n ? new Error(`Pagination source returned cursor \"${String(page.nextCursor)}\" more than once; loading stopped to prevent a duplicate-page loop.`)\n : null;\n // Publish after clearing the one-flight latch: load-all's effect observes\n // this terminal page state and may immediately schedule exactly one more.\n inFlightRef.current = false;\n controllerRef.current = null;\n publish({\n queryKey: requestKey,\n initialCursor: requestInitialCursor,\n rows,\n nextCursor: error ? null : page.nextCursor,\n totalItems: page.totalItems ?? snapshotRef.current.totalItems,\n error,\n phase: error ? \"cursor-error\" : \"ready\",\n });\n if (error) optionsRef.current.onError?.(error);\n if (error) setLoadAllRequested(false);\n } catch (raw) {\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal) || isAbort(raw)) return;\n const error = pgErrorToError(raw);\n publish({ ...snapshotRef.current, queryKey: requestKey, initialCursor: requestInitialCursor, error, phase: kind === \"initial\" ? \"initial-error\" : \"next-error\" });\n setLoadAllRequested(false);\n optionsRef.current.onError?.(error);\n } finally {\n if (isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) {\n inFlightRef.current = false;\n controllerRef.current = null;\n }\n }\n }, [isCurrent, publish]);\n\n useEffect(() => {\n generationRef.current += 1;\n const generation = generationRef.current;\n controllerRef.current?.abort();\n controllerRef.current = null;\n inFlightRef.current = false;\n receiptsRef.current = new Set<C>();\n setLoadAllRequested(false);\n if (!enabled) {\n publish({ queryKey, initialCursor, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"idle\" });\n return undefined;\n }\n // A former auth/org/filter identity must not remain visible for one paint.\n publish({ queryKey, initialCursor, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"initial\" });\n void requestPage(initialCursor, \"initial\", queryKey, initialCursor, generation);\n return () => controllerRef.current?.abort();\n }, [enabled, initialCursor, publish, queryKey, refreshTick, requestPage]);\n\n const loadNextPage = useCallback(async (): Promise<void> => {\n const current = snapshotRef.current;\n const requestKey = queryKeyRef.current;\n if (!enabledRef.current || inFlightRef.current || current.queryKey !== requestKey || current.initialCursor !== initialCursorRef.current) return;\n const generation = generationRef.current;\n if (current.phase === \"initial-error\") {\n receiptsRef.current = new Set<C>();\n await requestPage(optionsRef.current.initialCursor, \"initial\", requestKey, optionsRef.current.initialCursor, generation);\n return;\n }\n if (current.phase === \"cursor-error\" || current.nextCursor === null) return;\n if (receiptsRef.current.has(current.nextCursor)) {\n const error = new Error(`Pagination source tried to load cursor \"${String(current.nextCursor)}\" twice; loading stopped to prevent a duplicate-page loop.`);\n publish({ ...current, error, nextCursor: null, phase: \"cursor-error\" });\n optionsRef.current.onError?.(error);\n return;\n }\n await requestPage(current.nextCursor, \"next\", requestKey, current.initialCursor ?? optionsRef.current.initialCursor, generation);\n }, [publish, requestPage]);\n\n const refresh = useCallback(() => setRefreshTick((tick) => tick + 1), []);\n const requestLoadAll = useCallback(() => setLoadAllRequested(true), []);\n const stopLoadingAll = useCallback(() => setLoadAllRequested(false), []);\n useEffect(() => {\n if (!loadAllRequested || inFlightRef.current) return;\n const current = snapshotRef.current;\n if (current.phase === \"ready\" && current.nextCursor !== null) void loadNextPage();\n else if (current.phase === \"cursor-error\" || current.nextCursor === null) setLoadAllRequested(false);\n }, [loadAllRequested, loadNextPage, snapshot]);\n const visible = enabled && snapshot.queryKey === queryKey && snapshot.initialCursor === initialCursor;\n const phase = visible ? snapshot.phase : \"initial\";\n return {\n queryKey,\n rows: visible ? snapshot.rows : [],\n loading: enabled && phase === \"initial\",\n isFetchingNextPage: visible && phase === \"next\",\n error: visible ? snapshot.error : null,\n hasNextPage: visible && phase !== \"cursor-error\" && (snapshot.nextCursor !== null || phase === \"initial-error\"),\n loadNextPage,\n refresh,\n totalItems: visible ? snapshot.totalItems : undefined,\n isLoadingAll: visible && loadAllRequested,\n requestLoadAll,\n stopLoadingAll,\n };\n}\n","/** Framework-free validation shared by every host that resolves tables.pagination. */\nexport type ScrollPaginationPolicy =\n | { mode: \"scroll\"; thresholdPx: number; intentTimeoutMs: number; valid: true }\n /** `approvedBy` is recorded attribution from the configured policy, not an authentication assertion. */\n | { mode: \"manual\"; reason: string; approvedBy: string; valid: true }\n | { mode: \"suspended\"; reason: string; valid: false };\n\nexport function resolveScrollPaginationPolicy(values: {\n mode: unknown;\n thresholdPx: unknown;\n intentTimeoutMs: unknown;\n reason?: unknown;\n approvedBy?: unknown;\n}): ScrollPaginationPolicy {\n const thresholdPx = values.thresholdPx;\n const intentTimeoutMs = values.intentTimeoutMs;\n const dimensionsValid = typeof thresholdPx === \"number\" && Number.isFinite(thresholdPx) && thresholdPx >= 0 &&\n typeof intentTimeoutMs === \"number\" && Number.isFinite(intentTimeoutMs) && intentTimeoutMs > 0;\n if (values.mode === \"scroll\") {\n return dimensionsValid\n ? { mode: \"scroll\", thresholdPx, intentTimeoutMs, valid: true }\n : suspendScrollPaginationPolicy(\"Automatic loading is paused because its scrolling preferences are missing or invalid.\");\n }\n if (values.mode === \"manual\") {\n const reason = nonBlankString(values.reason);\n const approvedBy = nonBlankString(values.approvedBy);\n return reason && approvedBy\n ? { mode: \"manual\", reason, approvedBy, valid: true }\n : suspendScrollPaginationPolicy(\"Automatic loading is paused because the manual exception is missing its reason or approving owner.\");\n }\n return suspendScrollPaginationPolicy(\"Automatic loading is paused because its pagination mode is missing or invalid.\");\n}\n\n/** A temporary configuration state keeps explicit Load more usable without inventing an approval. */\nexport function suspendScrollPaginationPolicy(reason: string): ScrollPaginationPolicy {\n return { mode: \"suspended\", reason, valid: false };\n}\n\nfunction nonBlankString(value: unknown): string | null {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : null;\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","isAbort"]}
1
+ {"version":3,"sources":["../src/react.ts","../src/react/use-async-data.ts","../src/net/errors.ts","../src/index.ts","../src/react/use-realtime-channel.ts","../src/db/realtime.ts","../src/react/use-realtime-query.ts","../src/react/use-async-action.ts","../src/react/use-paginated-data.ts","../src/pagination/scroll-policy.ts"],"sourcesContent":["\"use client\";\n\n/**\n * `@ai-matrx/data/react` — the React bindings for the data layer.\n *\n * Three hooks, each built at a MEASURED repeated consumer (C25 — no\n * speculation): a read (`useAsyncData`, 93 hand-wired copies), a live read\n * (`useRealtimeChannel` / `useRealtimeQuery`, 18 hand-wired copies), and a\n * write (`useAsyncAction`, 554 hand-wired pending booleans). Every hook's\n * header names the specific production defects its hand-rolled twins carry.\n *\n * This is NOT a data-fetching framework and never becomes one. It holds no\n * cache, no store, no query client and no global state — it does not compete\n * with the host's Redux or React Query, it composes under either. What it owns\n * is the part every copy gets wrong: cancellation, stale-response ordering,\n * honest error conversion, retry classification, reconnection, and\n * double-submit refusal.\n *\n * Every entry here is stamped `\"use client\"`.\n */\n\nexport {\n useAsyncData,\n type AsyncData,\n type AsyncDataContext,\n type UseAsyncDataOptions,\n} from \"./react/use-async-data\";\n\nexport {\n useRealtimeChannel,\n type PostgresChangeBinding,\n type PostgresChangeEvent,\n type PostgresChangeFilter,\n type PostgresChangePayload,\n type RealtimeChannelLike,\n type RealtimeChannelState,\n type RealtimeClientLike,\n type RealtimeStatus,\n type UseRealtimeChannelOptions,\n} from \"./react/use-realtime-channel\";\n\nexport {\n useRealtimeQuery,\n type RealtimeQuery,\n type UseRealtimeQueryOptions,\n type WatchedTable,\n} from \"./react/use-realtime-query\";\n\nexport {\n ActionInFlightError,\n useAsyncAction,\n type ActionResult,\n type AsyncAction,\n type UseAsyncActionOptions,\n} from \"./react/use-async-action\";\n\nexport {\n usePaginatedData,\n type PaginatedData,\n type PaginatedDataOptions,\n type PaginatedPage,\n} from \"./react/use-paginated-data\";\n\nexport {\n resolveScrollPaginationPolicy,\n suspendScrollPaginationPolicy,\n type ScrollPaginationPolicy,\n} from \"./pagination/scroll-policy\";\n","\"use client\";\n\n/**\n * `useAsyncData` — the one honest way to read data into a React component.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 93 files hand-wire the exact\n * same five pieces — a `cancelled` flag, a `loading` boolean, an `error`\n * string, a refetch tick, and a `.catch()`. Each copy re-derives the same\n * mistakes, and the census found them all in the wild:\n *\n * 1. `setError(String(err))` — a PostgREST error is a PLAIN OBJECT, not an\n * `Error`, so `String(err)` renders \"[object Object]\" to the user and the\n * real `message`/`code`/`hint` are lost. This hook runs every failure\n * through `pgErrorToError`.\n * 2. No `AbortSignal` reaches the loader, so a superseded request keeps\n * running to completion, burning a connection and (worse) still counting\n * against rate limits. This hook aborts the previous run.\n * 3. Stale-response ordering — nothing stops a slow first request from\n * overwriting a fast second one. This hook stamps every run and ignores\n * any result that is not the newest.\n * 4. `loading` flips true on every key change, blanking the screen even when\n * previous data is still perfectly displayable. `keepPreviousData` is ON\n * by default and `refreshing` reports the background load honestly.\n * 5. An abort is treated as a failure and painted as an error banner. Here an\n * abort is silent by construction — it is not a failure, it is a\n * cancellation the component itself asked for.\n * 6. No retry, so one transient network blip is a permanent empty state.\n * Transient TRANSPORT failures retry with backoff; a server that answered\n * (a 400, an RLS denial) never does — retrying those is how you turn one\n * bug into a storm.\n *\n * Nothing fails silently: a terminal failure always lands in `error`, and\n * `onError` fires with the same real `Error`.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\nimport { isNetError, toNetError } from \"../net/errors\";\n\nexport interface UseAsyncDataOptions<T> {\n /**\n * Skip loading entirely (e.g. the key is null). Default `true`.\n * While disabled: `loading` is false and existing data is retained.\n */\n enabled?: boolean;\n /** Shown before the first load resolves. */\n initialData?: T;\n /**\n * Keep the previous key's data visible while the new key loads.\n * Default `true` — a list that blanks on every filter change is a worse\n * experience than a list that is briefly one keystroke stale.\n */\n keepPreviousData?: boolean;\n /**\n * How many times to retry a TRANSIENT TRANSPORT failure. Default `2`.\n * Errors the server actually answered with are never retried.\n */\n retry?: number;\n /** First backoff delay in ms; doubles per attempt. Default `300`. */\n retryDelayMs?: number;\n /** Called once per terminal failure, with the real `Error`. */\n onError?: (error: Error) => void;\n}\n\nexport interface AsyncData<T> {\n /** `undefined` until the first load resolves (or `initialData`). */\n data: T | undefined;\n /** True only while there is nothing to show yet. */\n loading: boolean;\n /** True while a load runs over data that is already on screen. */\n refreshing: boolean;\n /** The last terminal failure, as a real `Error`. Cleared on success. */\n error: Error | null;\n /** Re-run the loader now. Safe to call from an event handler or effect. */\n refresh: () => void;\n}\n\nexport interface AsyncDataContext {\n signal: AbortSignal;\n}\n\nfunction isAbort(error: unknown): boolean {\n if (error instanceof DOMException && error.name === \"AbortError\") return true;\n if (error instanceof Error && error.name === \"AbortError\") return true;\n return false;\n}\n\n/** Only a transport that never got an answer is worth retrying. */\nfunction isRetryable(error: unknown): boolean {\n const net = isNetError(error) ? error : toNetError(error);\n return net.retryable;\n}\n\nconst sleep = (ms: number, signal: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n function onAbort(): void {\n clearTimeout(timer);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n\nexport function useAsyncData<T>(\n load: (context: AsyncDataContext) => Promise<T>,\n deps: readonly unknown[],\n options: UseAsyncDataOptions<T> = {},\n): AsyncData<T> {\n const {\n enabled = true,\n initialData,\n keepPreviousData = true,\n retry = 2,\n retryDelayMs = 300,\n onError,\n } = options;\n\n const [data, setData] = useState<T | undefined>(initialData);\n const [loading, setLoading] = useState<boolean>(enabled);\n const [refreshing, setRefreshing] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n const [tick, setTick] = useState(0);\n\n // Latest-value refs so a changed callback identity never re-triggers a load\n // (the host runs the React Compiler; the package must be correct without it).\n const loadRef = useRef(load);\n loadRef.current = load;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const runIdRef = useRef(0);\n const hasDataRef = useRef(initialData !== undefined);\n\n const refresh = useCallback(() => setTick((n) => n + 1), []);\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n setRefreshing(false);\n return undefined;\n }\n\n const runId = ++runIdRef.current;\n const controller = new AbortController();\n const showingData = hasDataRef.current && keepPreviousData;\n\n if (!keepPreviousData) {\n hasDataRef.current = false;\n setData(undefined);\n }\n setLoading(!showingData);\n setRefreshing(showingData);\n\n void (async () => {\n let attempt = 0;\n for (;;) {\n try {\n const result = await loadRef.current({ signal: controller.signal });\n if (runId !== runIdRef.current || controller.signal.aborted) return;\n hasDataRef.current = true;\n setData(result);\n setError(null);\n setLoading(false);\n setRefreshing(false);\n return;\n } catch (raw) {\n if (isAbort(raw) || controller.signal.aborted) return;\n if (runId !== runIdRef.current) return;\n if (attempt < retry && isRetryable(raw)) {\n attempt += 1;\n try {\n await sleep(retryDelayMs * 2 ** (attempt - 1), controller.signal);\n } catch {\n return;\n }\n continue;\n }\n const failure = pgErrorToError(raw);\n setError(failure);\n setLoading(false);\n setRefreshing(false);\n onErrorRef.current?.(failure);\n return;\n }\n }\n })();\n\n return () => {\n controller.abort();\n };\n // `deps` is the caller's declared key set — the whole point of the hook.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, keepPreviousData, retry, retryDelayMs, tick, ...deps]);\n\n return { data, loading, refreshing, error, refresh };\n}\n","/**\n * Structured errors for the resilience layer.\n *\n * Every network-layer failure resolves to one of these classes so callers\n * (and any recovery UI) can branch on error type, not on string matching.\n *\n * Ported verbatim from matrx-frontend `lib/net/errors.ts` — the origin of the\n * trio. The one coupling seam removed: `extractErrorMessage` (previously the\n * app's `utils/errors`) is pure logic and ships inside the package.\n */\n\nexport type NetErrorCode =\n | \"connect-timeout\"\n | \"total-timeout\"\n | \"heartbeat-timeout\"\n | \"network\"\n | \"http\"\n | \"aborted\"\n | \"offline\"\n | \"unknown\";\n\nexport class NetError extends Error {\n readonly code: NetErrorCode;\n override readonly cause?: unknown;\n readonly status?: number;\n readonly retryable: boolean;\n\n constructor(\n code: NetErrorCode,\n message: string,\n opts: { cause?: unknown; status?: number; retryable?: boolean } = {},\n ) {\n super(message);\n this.name = \"NetError\";\n this.code = code;\n this.cause = opts.cause;\n // Conditional only for `exactOptionalPropertyTypes` — same runtime shape.\n if (opts.status !== undefined) this.status = opts.status;\n this.retryable = opts.retryable ?? false;\n }\n}\n\nexport class ConnectTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\"connect-timeout\", `Connection timed out after ${ms}ms`, {\n cause,\n retryable: true,\n });\n this.name = \"ConnectTimeoutError\";\n }\n}\n\nexport class TotalTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\"total-timeout\", `Request exceeded ${ms}ms total budget`, {\n cause,\n retryable: false,\n });\n this.name = \"TotalTimeoutError\";\n }\n}\n\nexport class HeartbeatTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\n \"heartbeat-timeout\",\n `No server activity for ${ms}ms — stream considered dead`,\n { cause, retryable: false },\n );\n this.name = \"HeartbeatTimeoutError\";\n }\n}\n\nexport class NetworkError extends NetError {\n constructor(message: string, cause?: unknown) {\n super(\"network\", message, { cause, retryable: true });\n this.name = \"NetworkError\";\n }\n}\n\nexport class HttpError extends NetError {\n constructor(status: number, message: string, cause?: unknown) {\n // 408/429/5xx are generally safe to retry for idempotent ops.\n const retryable = status === 408 || status === 429 || status >= 500;\n super(\"http\", message, { cause, status, retryable });\n this.name = \"HttpError\";\n }\n}\n\nexport class AbortedError extends NetError {\n constructor(cause?: unknown) {\n super(\"aborted\", \"Request was aborted\", { cause, retryable: false });\n this.name = \"AbortedError\";\n }\n}\n\nexport class OfflineError extends NetError {\n constructor() {\n super(\"offline\", \"Browser is offline\", { retryable: true });\n this.name = \"OfflineError\";\n }\n}\n\nexport function isNetError(err: unknown): err is NetError {\n return err instanceof NetError;\n}\n\n/**\n * Folds any thrown value into a descriptive message string — THE one reader\n * for every failure shape this platform actually emits.\n *\n * 2026-09-07 (duplication census rows 9, 10, 21): this body had DRIFTED BEHIND\n * three host copies, which is the campaign's failure mode running in the\n * package's favour-direction. Each host had learned something the package\n * never did, so every non-frontend consumer of `@ai-matrx/data/net` was\n * flattening a server's own sentence into \"An unexpected error occurred\". All\n * three are folded in here, with a named regression test per shape:\n *\n * 1. **FastAPI's `detail`** (matrx-frontend `utils/errors.ts`, learned\n * 2026-08-31 as V2 finding G5). Our server refuses in `detail` — a string,\n * or an object carrying `message` — and every reader checked\n * `message`/`details` only. The server's sentence is the whole point of\n * catching this.\n * 2. **A `fallback` argument** (matrx-frontend `ShortcutForm.tsx`). A caller\n * who knows what it was doing can say \"Failed to save shortcut\" instead of\n * the generic sentence. Optional; the default is unchanged, so every\n * existing single-argument call behaves exactly as before.\n * 3. **Recursion into nested error objects** (aidream `workflow-studio`, two\n * byte-identical copies). The run pipeline nests: `{error: {type, message}}`\n * and `{error: {error: ...}}` and `{exception: {...}}` are all real shapes,\n * and a non-recursive reader answers \"An unexpected error occurred\" while\n * holding the actual reason two levels down. Depth-capped at 4 — a cyclic\n * or absurdly deep object must not hang a render.\n *\n * PostgREST-style errors are plain objects carrying\n * `message`/`details`/`hint`/`code`; a bare `String(err)` on those yields\n * \"[object Object]\", which is why this exists at all.\n */\nexport function extractErrorMessage(\n err: unknown,\n fallback = \"An unexpected error occurred\",\n): string {\n if (!err) return \"Unknown error\";\n if (typeof err === \"string\") return err;\n if (err instanceof Error) return err.message;\n if (typeof err === \"object\") {\n const e = err as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof e.message === \"string\" && e.message) parts.push(e.message);\n // FastAPI's own word for the reason — only when nothing said `message`,\n // so a PostgREST error keeps its exact composed sentence.\n if (parts.length === 0) {\n if (typeof e.detail === \"string\" && e.detail) parts.push(e.detail);\n else if (e.detail && typeof e.detail === \"object\") {\n const d = e.detail as Record<string, unknown>;\n if (typeof d.message === \"string\" && d.message) parts.push(d.message);\n }\n }\n if (typeof e.details === \"string\" && e.details) parts.push(e.details);\n if (typeof e.hint === \"string\" && e.hint) parts.push(`Hint: ${e.hint}`);\n if (typeof e.code === \"string\" && e.code) parts.push(`Code: ${e.code}`);\n if (parts.length > 0) return parts.join(\" · \");\n\n const nested = extractNestedErrorMessage(e, 0);\n if (nested !== null) return nested;\n\n try {\n const s = JSON.stringify(err);\n if (s && s !== \"{}\") return s;\n } catch {\n /* ignore */\n }\n }\n return fallback;\n}\n\n/** Depth-capped walk into `error` / `exception` / `cause`. See above. */\nfunction extractNestedErrorMessage(\n e: Record<string, unknown>,\n depth: number,\n): string | null {\n if (depth > 4) return null;\n for (const key of [\"error\", \"exception\", \"cause\"] as const) {\n const value = e[key];\n if (typeof value === \"string\" && value) return value;\n if (value && typeof value === \"object\") {\n const inner = value as Record<string, unknown>;\n if (typeof inner.message === \"string\" && inner.message) return inner.message;\n if (typeof inner.detail === \"string\" && inner.detail) return inner.detail;\n const deeper = extractNestedErrorMessage(inner, depth + 1);\n if (deeper !== null) return deeper;\n }\n }\n return null;\n}\n\n/**\n * True when a failure is the TRANSPORT dying, not the server answering: the\n * browser is offline, asleep, mid-wifi-handoff, or DNS/TLS failed. It also\n * covers the bounded Envoy/Supabase gateway shape emitted when the upstream\n * database connection dies before response headers. `fetch`\n * rejects with a bare `TypeError: Failed to fetch` in every browser, and\n * supabase-js surfaces that verbatim (message only — no code, no hint).\n *\n * Use it to decide how LOUD a failure is, never whether to handle it. A\n * transport failure carries nothing an engineer can act on, so logging it at\n * error level buries the failures that do — that is how a notes surface filed\n * a system_error every time a laptop slept (2026-08-11). Anything with a\n * Postgres code, an HTTP status, or a server message is NOT this: keep those\n * loud.\n */\nexport function isTransportFailure(err: unknown): boolean {\n if (err instanceof NetworkError || err instanceof OfflineError) return true;\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n if (err instanceof TypeError) return true;\n if (err && typeof err === \"object\") {\n const e = err as { code?: unknown; status?: unknown; message?: unknown; name?: unknown };\n // A real PostgREST/Postgres failure always carries a code or status.\n if (e.code || e.status) return false;\n const text = `${typeof e.name === \"string\" ? e.name : \"\"} ${\n typeof e.message === \"string\" ? e.message : \"\"\n }`.toLowerCase();\n return (\n text.includes(\"failed to fetch\") ||\n text.includes(\"networkerror\") ||\n text.includes(\"network request failed\") ||\n text.includes(\"load failed\") || // Safari's wording for the same condition\n text.includes(\"upstream connect error or disconnect/reset before headers\")\n );\n }\n return false;\n}\n\nexport function toNetError(err: unknown): NetError {\n if (isNetError(err)) return err;\n if (err instanceof DOMException && err.name === \"AbortError\") {\n return new AbortedError(err);\n }\n if (err instanceof TypeError) {\n // fetch() throws TypeError on DNS/connection failure in every browser.\n return new NetworkError(err.message || \"Network request failed\", err);\n }\n return new NetError(\n \"unknown\",\n extractErrorMessage(err),\n { cause: err },\n );\n}\n","/**\n * @ai-matrx/data — the core module.\n *\n * The shared structural vocabulary every lane of the data layer speaks, plus\n * the lane-free net-resilience trio (S12 rides the core — re-exported from\n * `./net`, also importable as `@ai-matrx/data/net`). This entry point\n * otherwise deliberately stays tiny (the split-out law): the Supabase lane is\n * `@ai-matrx/data/db`; the API and files lanes arrive in later minors. Nothing\n * here performs network, storage, or global-state work at import time.\n *\n * The types are STRUCTURAL on purpose: the package never imports\n * `@supabase/supabase-js` — a real `PostgrestError` satisfies\n * `PostgrestLikeError` by shape, and so does any compatible client, so the\n * package works against whatever client the host injects.\n */\n\n/** The shape of a PostgREST error object (plain object, NOT an Error). */\nexport interface PostgrestLikeError {\n message: string;\n details?: string | null;\n hint?: string | null;\n code?: string | null;\n}\n\n/** The shape a `.maybeSingle()` call resolves to. */\nexport interface MaybeSingleResponse<Row> {\n data: Row | null;\n error: PostgrestLikeError | null;\n}\n\n/** A JSON value — the wire-safe subset every column and payload speaks. */\nexport type Json =\n | string\n | number\n | boolean\n | null\n | { [key: string]: Json | undefined }\n | Json[];\n\n/**\n * Converts any thrown value from a Supabase/PostgREST call into a proper Error\n * with a descriptive message. PostgREST errors are plain objects with `message`,\n * `code`, `details`, and `hint` — they are NOT `instanceof Error`, which causes\n * generic catch blocks to lose the real reason.\n */\nexport function pgErrorToError(err: unknown): Error {\n if (err instanceof Error) return err;\n if (err && typeof err === \"object\") {\n const e = err as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof e.message === \"string\" && e.message) parts.push(e.message);\n if (typeof e.details === \"string\" && e.details)\n parts.push(`Details: ${e.details}`);\n if (typeof e.hint === \"string\" && e.hint) parts.push(`Hint: ${e.hint}`);\n if (typeof e.code === \"string\" && e.code) parts.push(`(code: ${e.code})`);\n if (parts.length > 0) return new Error(parts.join(\" — \"));\n }\n return new Error(String(err));\n}\n\nexport * from \"./credentials\";\nexport * from \"./net\";\n","\"use client\";\n\n/**\n * `useRealtimeChannel` — a Supabase Realtime subscription that stays alive.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 18 React modules open a\n * `postgres_changes` channel by hand. Only ONE of them reconnects. The rest\n * share a defect class that is invisible in testing and brutal in production:\n * a channel that drops (laptop sleep, a proxy idling the socket, a Realtime\n * deploy) never comes back, so the screen quietly stops updating and nothing\n * anywhere says so. The user believes stale data.\n *\n * Every hard part lives here:\n *\n * - **Unique topic per mount** (`uniqueChannelTopic`) — React 19\n * double-invokes effects and Fast Refresh re-runs them; a shared topic\n * collides with a still-joined channel and the second subscribe never\n * fires.\n * - **Reconnect with exponential backoff and a stability reset** — a channel\n * that stayed up for a while starts its next backoff from the bottom\n * instead of inheriting an hour-old delay.\n * - **Catch-up on reconnect** (`onReconnect`) — the events that happened\n * while the socket was down are GONE. Reconnecting without a re-read\n * leaves a permanently wrong screen; this hook makes the re-read part of\n * reconnecting rather than something each caller remembers.\n * - **Honest status** — `connecting | connected | reconnecting | disconnected`,\n * so a surface can tell the user it is stale instead of lying silently.\n * - **Latest-callback refs** — a new inline handler on every render must not\n * tear down and rebuild the subscription (the \"why does my realtime\n * reconnect forever\" bug).\n * - **Guaranteed teardown**, including for a channel that is still mid-join.\n *\n * The client type is STRUCTURAL: this package never imports\n * `@supabase/supabase-js`. A real Supabase client satisfies it by shape (there\n * is a compile-time proof of that in `use-realtime-channel.test.tsx`).\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { uniqueChannelTopic } from \"../db/realtime\";\n\nexport type RealtimeStatus =\n | \"connecting\"\n | \"connected\"\n | \"reconnecting\"\n | \"disconnected\";\n\nexport type PostgresChangeEvent = \"*\" | \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\n/** The payload shape `postgres_changes` delivers. */\nexport interface PostgresChangePayload {\n eventType?: string;\n new?: unknown;\n old?: unknown;\n}\n\nexport interface PostgresChangeBinding {\n event: PostgresChangeEvent;\n schema: string;\n table: string;\n /** PostgREST filter, e.g. `file_id=eq.<uuid>`. */\n filter?: string;\n onChange: (payload: PostgresChangePayload) => void;\n}\n\n/** The `postgres_changes` filter, exactly as Supabase declares it. */\nexport interface PostgresChangeFilter {\n event: PostgresChangeEvent;\n schema: string;\n table?: string;\n filter?: string;\n}\n\n/**\n * Structural stand-in for `RealtimeChannel`. The signature must match\n * Supabase's `postgres_changes` overload PRECISELY — a wider `filter` type or\n * a `never`-typed callback makes the real client fail to satisfy this\n * interface, and the host discovers it at its own typecheck. That is exactly\n * what the compile-time proof in `use-realtime-channel.test.tsx` caught during\n * this package's own build.\n */\nexport interface RealtimeChannelLike {\n on(\n type: \"postgres_changes\",\n filter: PostgresChangeFilter,\n callback: (payload: PostgresChangePayload) => void,\n ): RealtimeChannelLike;\n subscribe(callback?: (status: string, error?: Error) => void): unknown;\n}\n\n/** Structural stand-in for `SupabaseClient` (realtime surface only). */\nexport interface RealtimeClientLike {\n channel(topic: string): RealtimeChannelLike;\n removeChannel(channel: RealtimeChannelLike): unknown;\n}\n\nexport interface UseRealtimeChannelOptions {\n /** Turn the subscription off without unmounting. Default `true`. */\n enabled?: boolean;\n /**\n * Called after a DROPPED channel comes back. This is the catch-up door:\n * re-read whatever the missed events would have changed.\n */\n onReconnect?: () => void;\n /** First reconnect delay in ms; doubles up to `maxReconnectDelayMs`. Default `1000`. */\n reconnectDelayMs?: number;\n /** Ceiling for the reconnect backoff. Default `30000`. */\n maxReconnectDelayMs?: number;\n /**\n * How long a channel must stay connected before its backoff resets to the\n * floor. Default `30000` — without it a flapping connection inherits an\n * ever-growing delay and effectively stops trying.\n */\n backoffResetAfterMs?: number;\n}\n\nexport interface RealtimeChannelState {\n status: RealtimeStatus;\n /** True once the channel has dropped at least once this mount. */\n hasReconnected: boolean;\n}\n\nexport function useRealtimeChannel(\n client: RealtimeClientLike | null | undefined,\n topic: string,\n bindings: readonly PostgresChangeBinding[],\n options: UseRealtimeChannelOptions = {},\n): RealtimeChannelState {\n const {\n enabled = true,\n onReconnect,\n reconnectDelayMs = 1_000,\n maxReconnectDelayMs = 30_000,\n backoffResetAfterMs = 30_000,\n } = options;\n\n const [status, setStatus] = useState<RealtimeStatus>(\n enabled && client ? \"connecting\" : \"disconnected\",\n );\n const [hasReconnected, setHasReconnected] = useState(false);\n\n const bindingsRef = useRef(bindings);\n bindingsRef.current = bindings;\n const onReconnectRef = useRef(onReconnect);\n onReconnectRef.current = onReconnect;\n\n // The binding SHAPE (not the handler identities) is what must re-subscribe.\n const shape = bindings\n .map((b) => `${b.event}:${b.schema}.${b.table}:${b.filter ?? \"\"}`)\n .join(\"|\");\n\n useEffect(() => {\n if (!client || !enabled) {\n setStatus(\"disconnected\");\n return undefined;\n }\n\n let disposed = false;\n let attempt = 0;\n let everConnected = false;\n let connectedAt = 0;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let channel: RealtimeChannelLike | null = null;\n\n const teardown = (): void => {\n if (!channel) return;\n const dead = channel;\n channel = null;\n try {\n void client.removeChannel(dead);\n } catch {\n // A channel torn down mid-join can reject; the socket drops it anyway\n // and there is nothing a caller could do about it.\n }\n };\n\n const scheduleReconnect = (): void => {\n if (disposed) return;\n // A channel that held for a while earned a fresh backoff floor.\n if (everConnected && Date.now() - connectedAt >= backoffResetAfterMs) {\n attempt = 0;\n }\n const delay = Math.min(\n reconnectDelayMs * 2 ** attempt,\n maxReconnectDelayMs,\n );\n attempt += 1;\n setStatus(everConnected ? \"reconnecting\" : \"connecting\");\n retryTimer = setTimeout(() => {\n teardown();\n connect();\n }, delay);\n };\n\n const connect = (): void => {\n if (disposed) return;\n let next = client.channel(uniqueChannelTopic(topic));\n for (const [index, binding] of bindingsRef.current.entries()) {\n next = next.on(\n \"postgres_changes\",\n {\n event: binding.event,\n schema: binding.schema,\n table: binding.table,\n ...(binding.filter !== undefined ? { filter: binding.filter } : {}),\n },\n (payload: PostgresChangePayload) => {\n // Read through the ref so a re-rendered inline handler is used\n // without rebuilding the subscription.\n bindingsRef.current[index]?.onChange(payload);\n },\n );\n }\n channel = next;\n next.subscribe((subscribeStatus) => {\n if (disposed) return;\n if (subscribeStatus === \"SUBSCRIBED\") {\n const rejoined = everConnected;\n everConnected = true;\n connectedAt = Date.now();\n attempt = 0;\n setStatus(\"connected\");\n if (rejoined) {\n setHasReconnected(true);\n onReconnectRef.current?.();\n }\n return;\n }\n if (\n subscribeStatus === \"CHANNEL_ERROR\" ||\n subscribeStatus === \"TIMED_OUT\" ||\n subscribeStatus === \"CLOSED\"\n ) {\n scheduleReconnect();\n }\n });\n };\n\n connect();\n\n return () => {\n disposed = true;\n if (retryTimer !== undefined) clearTimeout(retryTimer);\n teardown();\n };\n }, [\n client,\n enabled,\n topic,\n shape,\n reconnectDelayMs,\n maxReconnectDelayMs,\n backoffResetAfterMs,\n ]);\n\n return { status, hasReconnected };\n}\n","/**\n * Realtime channel hygiene — RE-EXPORT ONLY. The canonical home is\n * `@ai-matrx/realtime`.\n *\n * Channel NAMING belongs to the realtime package (its work order, R1): it owns\n * the channel-naming registry, and `uniqueChannelTopic` is the React-19\n * double-invoked-effect collision guard that registry hands out. This package\n * kept the only copy while `@ai-matrx/realtime` was unpublished; that copy is\n * now DELETED and this module forwards, so the two can never drift and the\n * `#`-suffixed instance topics a Matrx app opens all come from one counter.\n *\n * Why forwarding matters and a second copy would not do: the old\n * implementation kept its sequence in a module-level `let`. With unsplit dual\n * ESM/CJS output a module can be instantiated twice in one process, so two\n * counters both starting at 1 hand out the same \"unique\" topic twice — exactly\n * the collision the function exists to prevent. The realtime package keeps the\n * counter in a `globalThis` slot (`Symbol.for`) and proves it across module\n * graphs in its packed-tarball canary.\n *\n * `@ai-matrx/realtime` is the deeper layer (zero runtime dependencies; it does\n * not and must not depend on `@ai-matrx/data`), so this direction keeps the\n * sibling graph acyclic — `node scripts/check_ts_sibling_graph.mjs`.\n *\n * New realtime work should not reach for this at all: declare a channel\n * namespace with `defineChannelNamespace` and let the manager assign the\n * instance topic. This export exists for the call sites that have not yet\n * migrated off hand-rolled `.channel(` blocks.\n */\n\nexport { uniqueChannelTopic } from \"@ai-matrx/realtime\";\n","\"use client\";\n\n/**\n * `useRealtimeQuery` — a read that stays current, in one call.\n *\n * This is the exact shape 8+ matrx-frontend hooks hand-assemble: load a list,\n * subscribe to the rows behind it, refresh on change. Composing the two\n * primitives here (rather than in each host hook) closes the gap none of the\n * hand-rolled copies close — a change that arrives while the socket is down is\n * never delivered, so reconnecting MUST re-read. `useRealtimeChannel`'s\n * `onReconnect` is wired to `refresh` for you.\n *\n * Realtime events are COALESCED: a bulk write firing 200 INSERTs must not\n * produce 200 reads. Changes inside `debounceMs` collapse into one refresh.\n */\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport {\n useAsyncData,\n type AsyncData,\n type AsyncDataContext,\n type UseAsyncDataOptions,\n} from \"./use-async-data\";\nimport {\n useRealtimeChannel,\n type PostgresChangeBinding,\n type PostgresChangeEvent,\n type RealtimeClientLike,\n type RealtimeStatus,\n type UseRealtimeChannelOptions,\n} from \"./use-realtime-channel\";\n\n/** A table to watch. `onChange` is optional — refreshing is the default. */\nexport interface WatchedTable {\n event?: PostgresChangeEvent;\n schema: string;\n table: string;\n filter?: string;\n}\n\nexport interface UseRealtimeQueryOptions<T>\n extends UseAsyncDataOptions<T>,\n Pick<\n UseRealtimeChannelOptions,\n \"reconnectDelayMs\" | \"maxReconnectDelayMs\" | \"backoffResetAfterMs\"\n > {\n /** Coalesce bursts of realtime events into one refresh. Default `120`ms. */\n debounceMs?: number;\n}\n\nexport interface RealtimeQuery<T> extends AsyncData<T> {\n realtimeStatus: RealtimeStatus;\n}\n\nexport function useRealtimeQuery<T>(\n args: {\n client: RealtimeClientLike | null | undefined;\n topic: string;\n watch: readonly WatchedTable[];\n load: (context: AsyncDataContext) => Promise<T>;\n deps: readonly unknown[];\n },\n options: UseRealtimeQueryOptions<T> = {},\n): RealtimeQuery<T> {\n const {\n debounceMs = 120,\n reconnectDelayMs,\n maxReconnectDelayMs,\n backoffResetAfterMs,\n ...dataOptions\n } = options;\n const query = useAsyncData(args.load, args.deps, dataOptions);\n\n const refreshRef = useRef(query.refresh);\n refreshRef.current = query.refresh;\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(\n () => () => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n },\n [],\n );\n\n const shape = args.watch\n .map((w) => `${w.event ?? \"*\"}:${w.schema}.${w.table}:${w.filter ?? \"\"}`)\n .join(\"|\");\n\n const bindings = useMemo<PostgresChangeBinding[]>(() => {\n const coalescedRefresh = (): void => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => {\n timerRef.current = undefined;\n refreshRef.current();\n }, debounceMs);\n };\n return args.watch.map((w) => ({\n event: w.event ?? \"*\",\n schema: w.schema,\n table: w.table,\n ...(w.filter !== undefined ? { filter: w.filter } : {}),\n onChange: coalescedRefresh,\n }));\n // `shape` is the stable identity of `args.watch`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shape, debounceMs]);\n\n const { status } = useRealtimeChannel(args.client, args.topic, bindings, {\n enabled: dataOptions.enabled ?? true,\n ...(reconnectDelayMs !== undefined ? { reconnectDelayMs } : {}),\n ...(maxReconnectDelayMs !== undefined ? { maxReconnectDelayMs } : {}),\n ...(backoffResetAfterMs !== undefined ? { backoffResetAfterMs } : {}),\n // The events lost while the socket was down are unrecoverable; the read is\n // the only way back to truth.\n onReconnect: () => refreshRef.current(),\n });\n\n return { ...query, realtimeStatus: status };\n}\n","\"use client\";\n\n/**\n * `useAsyncAction` — a write with honest pending/error state.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 554 components hold their own\n * `saving`/`submitting`/`deleting` boolean around an async call. The repeated\n * defects:\n *\n * 1. **No double-submit guard.** A disabled button is not one — a fast second\n * click, an Enter key, or a re-render race all get through, and the write\n * runs twice. Here a second `run` while one is in flight is REFUSED and\n * says so (`{ ok: false, error: ActionInFlightError }`) instead of quietly\n * duplicating a row.\n * 2. **`setState` after unmount**, because `finally { setSaving(false) }`\n * does not know the component is gone.\n * 3. **`String(err)` again** — a PostgREST error object renders as\n * \"[object Object]\". Every failure here goes through `pgErrorToError`.\n * 4. **The error is thrown AND stored, or neither.** This hook returns a\n * discriminated result so a caller can branch without try/catch, while\n * `error` holds the same `Error` for rendering. Nothing is swallowed:\n * the failure is in the return value, in `error`, and in `onError`.\n *\n * Writes are NEVER retried here — that is the whole point of the boundary. A\n * user-authored submission replayed after an ambiguous timeout is how you get\n * two invoices. Retry belongs on idempotent reads (`useAsyncData`).\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\n\n/** Thrown-shaped refusal when a second run overlaps an in-flight one. */\nexport class ActionInFlightError extends Error {\n constructor() {\n super(\n \"This action is already running. The duplicate submission was refused — \" +\n \"wait for the in-flight action to finish.\",\n );\n this.name = \"ActionInFlightError\";\n }\n}\n\nexport type ActionResult<R> =\n | { ok: true; value: R }\n | { ok: false; error: Error };\n\nexport interface UseAsyncActionOptions<R> {\n onSuccess?: (value: R) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface AsyncAction<Args extends readonly unknown[], R> {\n run: (...args: Args) => Promise<ActionResult<R>>;\n pending: boolean;\n /** The last failure, as a real `Error`. Cleared when a new run starts. */\n error: Error | null;\n /** Clear `error` without running anything. */\n reset: () => void;\n}\n\nexport function useAsyncAction<Args extends readonly unknown[], R>(\n action: (...args: Args) => Promise<R>,\n options: UseAsyncActionOptions<R> = {},\n): AsyncAction<Args, R> {\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const actionRef = useRef(action);\n actionRef.current = action;\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const inFlightRef = useRef(false);\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const run = useCallback(async (...args: Args): Promise<ActionResult<R>> => {\n if (inFlightRef.current) {\n const refusal = new ActionInFlightError();\n if (mountedRef.current) setError(refusal);\n optionsRef.current.onError?.(refusal);\n return { ok: false, error: refusal };\n }\n inFlightRef.current = true;\n if (mountedRef.current) {\n setPending(true);\n setError(null);\n }\n try {\n const value = await actionRef.current(...args);\n if (mountedRef.current) setPending(false);\n optionsRef.current.onSuccess?.(value);\n return { ok: true, value };\n } catch (raw) {\n const failure = pgErrorToError(raw);\n if (mountedRef.current) {\n setPending(false);\n setError(failure);\n }\n optionsRef.current.onError?.(failure);\n return { ok: false, error: failure };\n } finally {\n inFlightRef.current = false;\n }\n }, []);\n\n const reset = useCallback(() => setError(null), []);\n\n return { run, pending, error, reset };\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\n\nexport interface PaginatedPage<T, C extends string | number> {\n rows: T[];\n nextCursor: C | null;\n totalItems?: number;\n}\n\nexport interface PaginatedDataOptions<T, C extends string | number> {\n /** Immutable identity of every input that changes the source result. */\n queryKey: string;\n initialCursor: C;\n loadPage: (context: { cursor: C; signal: AbortSignal }) => Promise<PaginatedPage<T, C>>;\n getRowId: (row: T) => string;\n enabled?: boolean;\n onError?: (error: Error) => void;\n /**\n * Optional, opt-in interval for rebuilding the visible source window in the\n * background. Hidden documents and an active pagination request are skipped.\n */\n refreshIntervalMs?: number;\n /** Runs once immediately before an interval-driven source reload begins. */\n onBeforeBackgroundRefresh?: () => void | Promise<void>;\n}\n\nexport interface PaginatedData<T> {\n queryKey: string;\n rows: T[];\n loading: boolean;\n /** True while a periodic refresh rebuilds the current window behind its rows. */\n isRefreshing?: boolean;\n isFetchingNextPage: boolean;\n error: Error | null;\n hasNextPage: boolean;\n loadNextPage: () => Promise<void>;\n refresh: () => void;\n totalItems: number | undefined;\n isLoadingAll: boolean;\n requestLoadAll: () => void;\n stopLoadingAll: () => void;\n}\n\ntype Phase = \"idle\" | \"initial\" | \"ready\" | \"next\" | \"refreshing\" | \"refresh-error\" | \"initial-error\" | \"next-error\" | \"cursor-error\";\n\ninterface Snapshot<T, C extends string | number> {\n queryKey: string | null;\n initialCursor: C | null;\n rows: T[];\n nextCursor: C | null;\n totalItems: number | undefined;\n error: Error | null;\n phase: Phase;\n}\n\nfunction isAbort(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === \"AbortError\";\n}\n\nfunction mergeRows<T>(current: readonly T[], incoming: readonly T[], getRowId: (row: T) => string): T[] {\n const next = [...current];\n const indexById = new Map<string, number>();\n current.forEach((row, index) => indexById.set(getRowId(row), index));\n for (const row of incoming) {\n const id = getRowId(row);\n const existing = indexById.get(id);\n if (existing === undefined) {\n indexById.set(id, next.length);\n next.push(row);\n } else {\n // The server's newer representation wins without moving visible rows.\n next[existing] = row;\n }\n }\n return next;\n}\n\n/** Cursor accumulation with no cache and no table dependency. */\nexport function usePaginatedData<T, C extends string | number>(\n options: PaginatedDataOptions<T, C>,\n): PaginatedData<T> {\n const { queryKey, initialCursor, enabled = true } = options;\n const [refreshTick, setRefreshTick] = useState(0);\n const [loadAllRequested, setLoadAllRequested] = useState(false);\n const [snapshot, setSnapshot] = useState<Snapshot<T, C>>({\n queryKey: null, initialCursor: null, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"idle\",\n });\n const snapshotRef = useRef(snapshot);\n const queryKeyRef = useRef(queryKey);\n queryKeyRef.current = queryKey;\n const initialCursorRef = useRef(initialCursor);\n initialCursorRef.current = initialCursor;\n const enabledRef = useRef(enabled);\n enabledRef.current = enabled;\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const generationRef = useRef(0);\n const inFlightRef = useRef(false);\n const controllerRef = useRef<AbortController | null>(null);\n const receiptsRef = useRef(new Set<C>());\n\n const publish = useCallback((next: Snapshot<T, C>) => {\n snapshotRef.current = next;\n setSnapshot(next);\n }, []);\n const isCurrent = useCallback((requestKey: string, requestInitialCursor: C, generation: number, signal: AbortSignal) => (\n !signal.aborted && generation === generationRef.current && requestKey === queryKeyRef.current && requestInitialCursor === initialCursorRef.current && enabledRef.current\n ), []);\n\n const requestPage = useCallback(async (\n cursor: C, kind: \"initial\" | \"next\", requestKey: string, requestInitialCursor: C, generation: number,\n ): Promise<void> => {\n if (inFlightRef.current || !enabledRef.current || requestKey !== queryKeyRef.current) return;\n const controller = new AbortController();\n controllerRef.current = controller;\n inFlightRef.current = true;\n publish({ ...snapshotRef.current, queryKey: requestKey, initialCursor: requestInitialCursor, error: null, phase: kind });\n try {\n const page = await optionsRef.current.loadPage({ cursor, signal: controller.signal });\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n const rows = mergeRows(\n kind === \"initial\" ? [] : snapshotRef.current.rows,\n page.rows,\n optionsRef.current.getRowId,\n );\n receiptsRef.current.add(cursor);\n const repeatedCursor = page.nextCursor !== null && receiptsRef.current.has(page.nextCursor);\n const error = repeatedCursor\n ? new Error(`Pagination source returned cursor \"${String(page.nextCursor)}\" more than once; loading stopped to prevent a duplicate-page loop.`)\n : null;\n // Publish after clearing the one-flight latch: load-all's effect observes\n // this terminal page state and may immediately schedule exactly one more.\n inFlightRef.current = false;\n controllerRef.current = null;\n publish({\n queryKey: requestKey,\n initialCursor: requestInitialCursor,\n rows,\n nextCursor: error ? null : page.nextCursor,\n totalItems: page.totalItems ?? snapshotRef.current.totalItems,\n error,\n phase: error ? \"cursor-error\" : \"ready\",\n });\n if (error) optionsRef.current.onError?.(error);\n if (error) setLoadAllRequested(false);\n } catch (raw) {\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal) || isAbort(raw)) return;\n const error = pgErrorToError(raw);\n publish({ ...snapshotRef.current, queryKey: requestKey, initialCursor: requestInitialCursor, error, phase: kind === \"initial\" ? \"initial-error\" : \"next-error\" });\n setLoadAllRequested(false);\n optionsRef.current.onError?.(error);\n } finally {\n if (isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) {\n inFlightRef.current = false;\n controllerRef.current = null;\n }\n }\n }, [isCurrent, publish]);\n\n const refreshLoadedWindow = useCallback(async (): Promise<void> => {\n const current = snapshotRef.current;\n const requestKey = queryKeyRef.current;\n const requestInitialCursor = initialCursorRef.current;\n if (\n inFlightRef.current || !enabledRef.current || current.queryKey !== requestKey\n || current.initialCursor !== requestInitialCursor || current.phase === \"initial\"\n ) return;\n\n const generation = generationRef.current;\n const controller = new AbortController();\n controllerRef.current = controller;\n inFlightRef.current = true;\n // Keep the last complete receipt on screen until the replacement window is complete.\n publish({ ...current, error: null, phase: \"refreshing\" });\n try {\n await optionsRef.current.onBeforeBackgroundRefresh?.();\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n\n const targetRowCount = current.rows.length;\n const receipts = new Set<C>();\n let cursor = requestInitialCursor;\n let rows: T[] = [];\n let nextCursor: C | null = null;\n let totalItems = current.totalItems;\n while (true) {\n const page = await optionsRef.current.loadPage({ cursor, signal: controller.signal });\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n rows = mergeRows(rows, page.rows, optionsRef.current.getRowId);\n receipts.add(cursor);\n totalItems = page.totalItems ?? totalItems;\n nextCursor = page.nextCursor;\n if (nextCursor !== null && receipts.has(nextCursor)) {\n throw new Error(`Pagination source returned cursor \"${String(nextCursor)}\" more than once; background refresh stopped to prevent a duplicate-page loop.`);\n }\n // Rebuild at least the existing visible window, unless the new source ends first.\n if (rows.length >= targetRowCount || nextCursor === null) break;\n cursor = nextCursor;\n }\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n receiptsRef.current = receipts;\n publish({\n queryKey: requestKey,\n initialCursor: requestInitialCursor,\n rows,\n nextCursor,\n totalItems,\n error: null,\n phase: \"ready\",\n });\n } catch (raw) {\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal) || isAbort(raw)) return;\n const error = pgErrorToError(raw);\n // The callback may have advanced an unkeyed source cutoff. Retain the\n // visible receipt, but never append its old cursor chain to that source.\n publish({ ...current, error, phase: \"refresh-error\" });\n setLoadAllRequested(false);\n optionsRef.current.onError?.(error);\n } finally {\n if (isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) {\n inFlightRef.current = false;\n controllerRef.current = null;\n }\n }\n }, [isCurrent, publish]);\n\n useEffect(() => {\n generationRef.current += 1;\n const generation = generationRef.current;\n controllerRef.current?.abort();\n controllerRef.current = null;\n inFlightRef.current = false;\n receiptsRef.current = new Set<C>();\n setLoadAllRequested(false);\n if (!enabled) {\n publish({ queryKey, initialCursor, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"idle\" });\n return undefined;\n }\n // A former auth/org/filter identity must not remain visible for one paint.\n publish({ queryKey, initialCursor, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"initial\" });\n void requestPage(initialCursor, \"initial\", queryKey, initialCursor, generation);\n return () => controllerRef.current?.abort();\n }, [enabled, initialCursor, publish, queryKey, refreshTick, requestPage]);\n\n const loadNextPage = useCallback(async (): Promise<void> => {\n const current = snapshotRef.current;\n const requestKey = queryKeyRef.current;\n if (!enabledRef.current || inFlightRef.current || current.queryKey !== requestKey || current.initialCursor !== initialCursorRef.current) return;\n const generation = generationRef.current;\n if (current.phase === \"initial-error\") {\n receiptsRef.current = new Set<C>();\n await requestPage(optionsRef.current.initialCursor, \"initial\", requestKey, optionsRef.current.initialCursor, generation);\n return;\n }\n if (current.phase === \"cursor-error\" || current.phase === \"refresh-error\" || current.nextCursor === null) return;\n if (receiptsRef.current.has(current.nextCursor)) {\n const error = new Error(`Pagination source tried to load cursor \"${String(current.nextCursor)}\" twice; loading stopped to prevent a duplicate-page loop.`);\n publish({ ...current, error, nextCursor: null, phase: \"cursor-error\" });\n optionsRef.current.onError?.(error);\n return;\n }\n await requestPage(current.nextCursor, \"next\", requestKey, current.initialCursor ?? optionsRef.current.initialCursor, generation);\n }, [publish, requestPage]);\n\n const refresh = useCallback(() => setRefreshTick((tick) => tick + 1), []);\n const requestLoadAll = useCallback(() => setLoadAllRequested(true), []);\n const stopLoadingAll = useCallback(() => setLoadAllRequested(false), []);\n\n useEffect(() => {\n const interval = options.refreshIntervalMs;\n if (!enabled || interval === undefined || !Number.isFinite(interval) || interval <= 0) return undefined;\n const timer = window.setInterval(() => {\n if (typeof document !== \"undefined\" && document.hidden) return;\n void refreshLoadedWindow();\n }, interval);\n return () => window.clearInterval(timer);\n }, [enabled, options.refreshIntervalMs, refreshLoadedWindow]);\n\n useEffect(() => {\n if (!loadAllRequested || inFlightRef.current) return;\n const current = snapshotRef.current;\n if (current.phase === \"ready\" && current.nextCursor !== null) void loadNextPage();\n else if (current.phase === \"cursor-error\" || current.phase === \"refresh-error\" || current.nextCursor === null) setLoadAllRequested(false);\n }, [loadAllRequested, loadNextPage, snapshot]);\n const visible = enabled && snapshot.queryKey === queryKey && snapshot.initialCursor === initialCursor;\n const phase = visible ? snapshot.phase : \"initial\";\n return {\n queryKey,\n rows: visible ? snapshot.rows : [],\n loading: enabled && phase === \"initial\",\n isRefreshing: visible && phase === \"refreshing\",\n isFetchingNextPage: visible && phase === \"next\",\n error: visible ? snapshot.error : null,\n hasNextPage: visible && phase !== \"cursor-error\" && phase !== \"refresh-error\" && (snapshot.nextCursor !== null || phase === \"initial-error\"),\n loadNextPage,\n refresh,\n totalItems: visible ? snapshot.totalItems : undefined,\n isLoadingAll: visible && loadAllRequested,\n requestLoadAll,\n stopLoadingAll,\n };\n}\n","/** Framework-free validation shared by every host that resolves tables.pagination. */\nexport type ScrollPaginationPolicy =\n | { mode: \"scroll\"; thresholdPx: number; intentTimeoutMs: number; valid: true }\n /** `approvedBy` is recorded attribution from the configured policy, not an authentication assertion. */\n | { mode: \"manual\"; reason: string; approvedBy: string; valid: true }\n | { mode: \"suspended\"; reason: string; valid: false };\n\nexport function resolveScrollPaginationPolicy(values: {\n mode: unknown;\n thresholdPx: unknown;\n intentTimeoutMs: unknown;\n reason?: unknown;\n approvedBy?: unknown;\n}): ScrollPaginationPolicy {\n const thresholdPx = values.thresholdPx;\n const intentTimeoutMs = values.intentTimeoutMs;\n const dimensionsValid = typeof thresholdPx === \"number\" && Number.isFinite(thresholdPx) && thresholdPx >= 0 &&\n typeof intentTimeoutMs === \"number\" && Number.isFinite(intentTimeoutMs) && intentTimeoutMs > 0;\n if (values.mode === \"scroll\") {\n return dimensionsValid\n ? { mode: \"scroll\", thresholdPx, intentTimeoutMs, valid: true }\n : suspendScrollPaginationPolicy(\"Automatic loading is paused because its scrolling preferences are missing or invalid.\");\n }\n if (values.mode === \"manual\") {\n const reason = nonBlankString(values.reason);\n const approvedBy = nonBlankString(values.approvedBy);\n return reason && approvedBy\n ? { mode: \"manual\", reason, approvedBy, valid: true }\n : suspendScrollPaginationPolicy(\"Automatic loading is paused because the manual exception is missing its reason or approving owner.\");\n }\n return suspendScrollPaginationPolicy(\"Automatic loading is paused because its pagination mode is missing or invalid.\");\n}\n\n/** A temporary configuration state keeps explicit Load more usable without inventing an approval. */\nexport function suspendScrollPaginationPolicy(reason: string): ScrollPaginationPolicy {\n return { mode: \"suspended\", reason, valid: false };\n}\n\nfunction nonBlankString(value: unknown): string | null {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : null;\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_react","isAbort"]}
package/dist/react.d.cts CHANGED
@@ -168,11 +168,20 @@ interface PaginatedDataOptions<T, C extends string | number> {
168
168
  getRowId: (row: T) => string;
169
169
  enabled?: boolean;
170
170
  onError?: (error: Error) => void;
171
+ /**
172
+ * Optional, opt-in interval for rebuilding the visible source window in the
173
+ * background. Hidden documents and an active pagination request are skipped.
174
+ */
175
+ refreshIntervalMs?: number;
176
+ /** Runs once immediately before an interval-driven source reload begins. */
177
+ onBeforeBackgroundRefresh?: () => void | Promise<void>;
171
178
  }
172
179
  interface PaginatedData<T> {
173
180
  queryKey: string;
174
181
  rows: T[];
175
182
  loading: boolean;
183
+ /** True while a periodic refresh rebuilds the current window behind its rows. */
184
+ isRefreshing?: boolean;
176
185
  isFetchingNextPage: boolean;
177
186
  error: Error | null;
178
187
  hasNextPage: boolean;
package/dist/react.d.ts CHANGED
@@ -168,11 +168,20 @@ interface PaginatedDataOptions<T, C extends string | number> {
168
168
  getRowId: (row: T) => string;
169
169
  enabled?: boolean;
170
170
  onError?: (error: Error) => void;
171
+ /**
172
+ * Optional, opt-in interval for rebuilding the visible source window in the
173
+ * background. Hidden documents and an active pagination request are skipped.
174
+ */
175
+ refreshIntervalMs?: number;
176
+ /** Runs once immediately before an interval-driven source reload begins. */
177
+ onBeforeBackgroundRefresh?: () => void | Promise<void>;
171
178
  }
172
179
  interface PaginatedData<T> {
173
180
  queryKey: string;
174
181
  rows: T[];
175
182
  loading: boolean;
183
+ /** True while a periodic refresh rebuilds the current window behind its rows. */
184
+ isRefreshing?: boolean;
176
185
  isFetchingNextPage: boolean;
177
186
  error: Error | null;
178
187
  hasNextPage: boolean;
package/dist/react.js CHANGED
@@ -527,6 +527,62 @@ function usePaginatedData(options) {
527
527
  }
528
528
  }
529
529
  }, [isCurrent, publish]);
530
+ const refreshLoadedWindow = useCallback3(async () => {
531
+ const current = snapshotRef.current;
532
+ const requestKey = queryKeyRef.current;
533
+ const requestInitialCursor = initialCursorRef.current;
534
+ if (inFlightRef.current || !enabledRef.current || current.queryKey !== requestKey || current.initialCursor !== requestInitialCursor || current.phase === "initial") return;
535
+ const generation = generationRef.current;
536
+ const controller = new AbortController();
537
+ controllerRef.current = controller;
538
+ inFlightRef.current = true;
539
+ publish({ ...current, error: null, phase: "refreshing" });
540
+ try {
541
+ await optionsRef.current.onBeforeBackgroundRefresh?.();
542
+ if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;
543
+ const targetRowCount = current.rows.length;
544
+ const receipts = /* @__PURE__ */ new Set();
545
+ let cursor = requestInitialCursor;
546
+ let rows = [];
547
+ let nextCursor = null;
548
+ let totalItems = current.totalItems;
549
+ while (true) {
550
+ const page = await optionsRef.current.loadPage({ cursor, signal: controller.signal });
551
+ if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;
552
+ rows = mergeRows(rows, page.rows, optionsRef.current.getRowId);
553
+ receipts.add(cursor);
554
+ totalItems = page.totalItems ?? totalItems;
555
+ nextCursor = page.nextCursor;
556
+ if (nextCursor !== null && receipts.has(nextCursor)) {
557
+ throw new Error(`Pagination source returned cursor "${String(nextCursor)}" more than once; background refresh stopped to prevent a duplicate-page loop.`);
558
+ }
559
+ if (rows.length >= targetRowCount || nextCursor === null) break;
560
+ cursor = nextCursor;
561
+ }
562
+ if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;
563
+ receiptsRef.current = receipts;
564
+ publish({
565
+ queryKey: requestKey,
566
+ initialCursor: requestInitialCursor,
567
+ rows,
568
+ nextCursor,
569
+ totalItems,
570
+ error: null,
571
+ phase: "ready"
572
+ });
573
+ } catch (raw) {
574
+ if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal) || isAbort2(raw)) return;
575
+ const error = pgErrorToError(raw);
576
+ publish({ ...current, error, phase: "refresh-error" });
577
+ setLoadAllRequested(false);
578
+ optionsRef.current.onError?.(error);
579
+ } finally {
580
+ if (isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) {
581
+ inFlightRef.current = false;
582
+ controllerRef.current = null;
583
+ }
584
+ }
585
+ }, [isCurrent, publish]);
530
586
  useEffect5(() => {
531
587
  generationRef.current += 1;
532
588
  const generation = generationRef.current;
@@ -553,7 +609,7 @@ function usePaginatedData(options) {
553
609
  await requestPage(optionsRef.current.initialCursor, "initial", requestKey, optionsRef.current.initialCursor, generation);
554
610
  return;
555
611
  }
556
- if (current.phase === "cursor-error" || current.nextCursor === null) return;
612
+ if (current.phase === "cursor-error" || current.phase === "refresh-error" || current.nextCursor === null) return;
557
613
  if (receiptsRef.current.has(current.nextCursor)) {
558
614
  const error = new Error(`Pagination source tried to load cursor "${String(current.nextCursor)}" twice; loading stopped to prevent a duplicate-page loop.`);
559
615
  publish({ ...current, error, nextCursor: null, phase: "cursor-error" });
@@ -565,11 +621,20 @@ function usePaginatedData(options) {
565
621
  const refresh = useCallback3(() => setRefreshTick((tick) => tick + 1), []);
566
622
  const requestLoadAll = useCallback3(() => setLoadAllRequested(true), []);
567
623
  const stopLoadingAll = useCallback3(() => setLoadAllRequested(false), []);
624
+ useEffect5(() => {
625
+ const interval = options.refreshIntervalMs;
626
+ if (!enabled || interval === void 0 || !Number.isFinite(interval) || interval <= 0) return void 0;
627
+ const timer = window.setInterval(() => {
628
+ if (typeof document !== "undefined" && document.hidden) return;
629
+ void refreshLoadedWindow();
630
+ }, interval);
631
+ return () => window.clearInterval(timer);
632
+ }, [enabled, options.refreshIntervalMs, refreshLoadedWindow]);
568
633
  useEffect5(() => {
569
634
  if (!loadAllRequested || inFlightRef.current) return;
570
635
  const current = snapshotRef.current;
571
636
  if (current.phase === "ready" && current.nextCursor !== null) void loadNextPage();
572
- else if (current.phase === "cursor-error" || current.nextCursor === null) setLoadAllRequested(false);
637
+ else if (current.phase === "cursor-error" || current.phase === "refresh-error" || current.nextCursor === null) setLoadAllRequested(false);
573
638
  }, [loadAllRequested, loadNextPage, snapshot]);
574
639
  const visible = enabled && snapshot.queryKey === queryKey && snapshot.initialCursor === initialCursor;
575
640
  const phase = visible ? snapshot.phase : "initial";
@@ -577,9 +642,10 @@ function usePaginatedData(options) {
577
642
  queryKey,
578
643
  rows: visible ? snapshot.rows : [],
579
644
  loading: enabled && phase === "initial",
645
+ isRefreshing: visible && phase === "refreshing",
580
646
  isFetchingNextPage: visible && phase === "next",
581
647
  error: visible ? snapshot.error : null,
582
- hasNextPage: visible && phase !== "cursor-error" && (snapshot.nextCursor !== null || phase === "initial-error"),
648
+ hasNextPage: visible && phase !== "cursor-error" && phase !== "refresh-error" && (snapshot.nextCursor !== null || phase === "initial-error"),
583
649
  loadNextPage,
584
650
  refresh,
585
651
  totalItems: visible ? snapshot.totalItems : void 0,
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react/use-async-data.ts","../src/net/errors.ts","../src/index.ts","../src/react/use-realtime-channel.ts","../src/db/realtime.ts","../src/react/use-realtime-query.ts","../src/react/use-async-action.ts","../src/react/use-paginated-data.ts","../src/pagination/scroll-policy.ts"],"sourcesContent":["\"use client\";\n\n/**\n * `useAsyncData` — the one honest way to read data into a React component.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 93 files hand-wire the exact\n * same five pieces — a `cancelled` flag, a `loading` boolean, an `error`\n * string, a refetch tick, and a `.catch()`. Each copy re-derives the same\n * mistakes, and the census found them all in the wild:\n *\n * 1. `setError(String(err))` — a PostgREST error is a PLAIN OBJECT, not an\n * `Error`, so `String(err)` renders \"[object Object]\" to the user and the\n * real `message`/`code`/`hint` are lost. This hook runs every failure\n * through `pgErrorToError`.\n * 2. No `AbortSignal` reaches the loader, so a superseded request keeps\n * running to completion, burning a connection and (worse) still counting\n * against rate limits. This hook aborts the previous run.\n * 3. Stale-response ordering — nothing stops a slow first request from\n * overwriting a fast second one. This hook stamps every run and ignores\n * any result that is not the newest.\n * 4. `loading` flips true on every key change, blanking the screen even when\n * previous data is still perfectly displayable. `keepPreviousData` is ON\n * by default and `refreshing` reports the background load honestly.\n * 5. An abort is treated as a failure and painted as an error banner. Here an\n * abort is silent by construction — it is not a failure, it is a\n * cancellation the component itself asked for.\n * 6. No retry, so one transient network blip is a permanent empty state.\n * Transient TRANSPORT failures retry with backoff; a server that answered\n * (a 400, an RLS denial) never does — retrying those is how you turn one\n * bug into a storm.\n *\n * Nothing fails silently: a terminal failure always lands in `error`, and\n * `onError` fires with the same real `Error`.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\nimport { isNetError, toNetError } from \"../net/errors\";\n\nexport interface UseAsyncDataOptions<T> {\n /**\n * Skip loading entirely (e.g. the key is null). Default `true`.\n * While disabled: `loading` is false and existing data is retained.\n */\n enabled?: boolean;\n /** Shown before the first load resolves. */\n initialData?: T;\n /**\n * Keep the previous key's data visible while the new key loads.\n * Default `true` — a list that blanks on every filter change is a worse\n * experience than a list that is briefly one keystroke stale.\n */\n keepPreviousData?: boolean;\n /**\n * How many times to retry a TRANSIENT TRANSPORT failure. Default `2`.\n * Errors the server actually answered with are never retried.\n */\n retry?: number;\n /** First backoff delay in ms; doubles per attempt. Default `300`. */\n retryDelayMs?: number;\n /** Called once per terminal failure, with the real `Error`. */\n onError?: (error: Error) => void;\n}\n\nexport interface AsyncData<T> {\n /** `undefined` until the first load resolves (or `initialData`). */\n data: T | undefined;\n /** True only while there is nothing to show yet. */\n loading: boolean;\n /** True while a load runs over data that is already on screen. */\n refreshing: boolean;\n /** The last terminal failure, as a real `Error`. Cleared on success. */\n error: Error | null;\n /** Re-run the loader now. Safe to call from an event handler or effect. */\n refresh: () => void;\n}\n\nexport interface AsyncDataContext {\n signal: AbortSignal;\n}\n\nfunction isAbort(error: unknown): boolean {\n if (error instanceof DOMException && error.name === \"AbortError\") return true;\n if (error instanceof Error && error.name === \"AbortError\") return true;\n return false;\n}\n\n/** Only a transport that never got an answer is worth retrying. */\nfunction isRetryable(error: unknown): boolean {\n const net = isNetError(error) ? error : toNetError(error);\n return net.retryable;\n}\n\nconst sleep = (ms: number, signal: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n function onAbort(): void {\n clearTimeout(timer);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n\nexport function useAsyncData<T>(\n load: (context: AsyncDataContext) => Promise<T>,\n deps: readonly unknown[],\n options: UseAsyncDataOptions<T> = {},\n): AsyncData<T> {\n const {\n enabled = true,\n initialData,\n keepPreviousData = true,\n retry = 2,\n retryDelayMs = 300,\n onError,\n } = options;\n\n const [data, setData] = useState<T | undefined>(initialData);\n const [loading, setLoading] = useState<boolean>(enabled);\n const [refreshing, setRefreshing] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n const [tick, setTick] = useState(0);\n\n // Latest-value refs so a changed callback identity never re-triggers a load\n // (the host runs the React Compiler; the package must be correct without it).\n const loadRef = useRef(load);\n loadRef.current = load;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const runIdRef = useRef(0);\n const hasDataRef = useRef(initialData !== undefined);\n\n const refresh = useCallback(() => setTick((n) => n + 1), []);\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n setRefreshing(false);\n return undefined;\n }\n\n const runId = ++runIdRef.current;\n const controller = new AbortController();\n const showingData = hasDataRef.current && keepPreviousData;\n\n if (!keepPreviousData) {\n hasDataRef.current = false;\n setData(undefined);\n }\n setLoading(!showingData);\n setRefreshing(showingData);\n\n void (async () => {\n let attempt = 0;\n for (;;) {\n try {\n const result = await loadRef.current({ signal: controller.signal });\n if (runId !== runIdRef.current || controller.signal.aborted) return;\n hasDataRef.current = true;\n setData(result);\n setError(null);\n setLoading(false);\n setRefreshing(false);\n return;\n } catch (raw) {\n if (isAbort(raw) || controller.signal.aborted) return;\n if (runId !== runIdRef.current) return;\n if (attempt < retry && isRetryable(raw)) {\n attempt += 1;\n try {\n await sleep(retryDelayMs * 2 ** (attempt - 1), controller.signal);\n } catch {\n return;\n }\n continue;\n }\n const failure = pgErrorToError(raw);\n setError(failure);\n setLoading(false);\n setRefreshing(false);\n onErrorRef.current?.(failure);\n return;\n }\n }\n })();\n\n return () => {\n controller.abort();\n };\n // `deps` is the caller's declared key set — the whole point of the hook.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, keepPreviousData, retry, retryDelayMs, tick, ...deps]);\n\n return { data, loading, refreshing, error, refresh };\n}\n","/**\n * Structured errors for the resilience layer.\n *\n * Every network-layer failure resolves to one of these classes so callers\n * (and any recovery UI) can branch on error type, not on string matching.\n *\n * Ported verbatim from matrx-frontend `lib/net/errors.ts` — the origin of the\n * trio. The one coupling seam removed: `extractErrorMessage` (previously the\n * app's `utils/errors`) is pure logic and ships inside the package.\n */\n\nexport type NetErrorCode =\n | \"connect-timeout\"\n | \"total-timeout\"\n | \"heartbeat-timeout\"\n | \"network\"\n | \"http\"\n | \"aborted\"\n | \"offline\"\n | \"unknown\";\n\nexport class NetError extends Error {\n readonly code: NetErrorCode;\n override readonly cause?: unknown;\n readonly status?: number;\n readonly retryable: boolean;\n\n constructor(\n code: NetErrorCode,\n message: string,\n opts: { cause?: unknown; status?: number; retryable?: boolean } = {},\n ) {\n super(message);\n this.name = \"NetError\";\n this.code = code;\n this.cause = opts.cause;\n // Conditional only for `exactOptionalPropertyTypes` — same runtime shape.\n if (opts.status !== undefined) this.status = opts.status;\n this.retryable = opts.retryable ?? false;\n }\n}\n\nexport class ConnectTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\"connect-timeout\", `Connection timed out after ${ms}ms`, {\n cause,\n retryable: true,\n });\n this.name = \"ConnectTimeoutError\";\n }\n}\n\nexport class TotalTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\"total-timeout\", `Request exceeded ${ms}ms total budget`, {\n cause,\n retryable: false,\n });\n this.name = \"TotalTimeoutError\";\n }\n}\n\nexport class HeartbeatTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\n \"heartbeat-timeout\",\n `No server activity for ${ms}ms — stream considered dead`,\n { cause, retryable: false },\n );\n this.name = \"HeartbeatTimeoutError\";\n }\n}\n\nexport class NetworkError extends NetError {\n constructor(message: string, cause?: unknown) {\n super(\"network\", message, { cause, retryable: true });\n this.name = \"NetworkError\";\n }\n}\n\nexport class HttpError extends NetError {\n constructor(status: number, message: string, cause?: unknown) {\n // 408/429/5xx are generally safe to retry for idempotent ops.\n const retryable = status === 408 || status === 429 || status >= 500;\n super(\"http\", message, { cause, status, retryable });\n this.name = \"HttpError\";\n }\n}\n\nexport class AbortedError extends NetError {\n constructor(cause?: unknown) {\n super(\"aborted\", \"Request was aborted\", { cause, retryable: false });\n this.name = \"AbortedError\";\n }\n}\n\nexport class OfflineError extends NetError {\n constructor() {\n super(\"offline\", \"Browser is offline\", { retryable: true });\n this.name = \"OfflineError\";\n }\n}\n\nexport function isNetError(err: unknown): err is NetError {\n return err instanceof NetError;\n}\n\n/**\n * Folds any thrown value into a descriptive message string — THE one reader\n * for every failure shape this platform actually emits.\n *\n * 2026-09-07 (duplication census rows 9, 10, 21): this body had DRIFTED BEHIND\n * three host copies, which is the campaign's failure mode running in the\n * package's favour-direction. Each host had learned something the package\n * never did, so every non-frontend consumer of `@ai-matrx/data/net` was\n * flattening a server's own sentence into \"An unexpected error occurred\". All\n * three are folded in here, with a named regression test per shape:\n *\n * 1. **FastAPI's `detail`** (matrx-frontend `utils/errors.ts`, learned\n * 2026-08-31 as V2 finding G5). Our server refuses in `detail` — a string,\n * or an object carrying `message` — and every reader checked\n * `message`/`details` only. The server's sentence is the whole point of\n * catching this.\n * 2. **A `fallback` argument** (matrx-frontend `ShortcutForm.tsx`). A caller\n * who knows what it was doing can say \"Failed to save shortcut\" instead of\n * the generic sentence. Optional; the default is unchanged, so every\n * existing single-argument call behaves exactly as before.\n * 3. **Recursion into nested error objects** (aidream `workflow-studio`, two\n * byte-identical copies). The run pipeline nests: `{error: {type, message}}`\n * and `{error: {error: ...}}` and `{exception: {...}}` are all real shapes,\n * and a non-recursive reader answers \"An unexpected error occurred\" while\n * holding the actual reason two levels down. Depth-capped at 4 — a cyclic\n * or absurdly deep object must not hang a render.\n *\n * PostgREST-style errors are plain objects carrying\n * `message`/`details`/`hint`/`code`; a bare `String(err)` on those yields\n * \"[object Object]\", which is why this exists at all.\n */\nexport function extractErrorMessage(\n err: unknown,\n fallback = \"An unexpected error occurred\",\n): string {\n if (!err) return \"Unknown error\";\n if (typeof err === \"string\") return err;\n if (err instanceof Error) return err.message;\n if (typeof err === \"object\") {\n const e = err as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof e.message === \"string\" && e.message) parts.push(e.message);\n // FastAPI's own word for the reason — only when nothing said `message`,\n // so a PostgREST error keeps its exact composed sentence.\n if (parts.length === 0) {\n if (typeof e.detail === \"string\" && e.detail) parts.push(e.detail);\n else if (e.detail && typeof e.detail === \"object\") {\n const d = e.detail as Record<string, unknown>;\n if (typeof d.message === \"string\" && d.message) parts.push(d.message);\n }\n }\n if (typeof e.details === \"string\" && e.details) parts.push(e.details);\n if (typeof e.hint === \"string\" && e.hint) parts.push(`Hint: ${e.hint}`);\n if (typeof e.code === \"string\" && e.code) parts.push(`Code: ${e.code}`);\n if (parts.length > 0) return parts.join(\" · \");\n\n const nested = extractNestedErrorMessage(e, 0);\n if (nested !== null) return nested;\n\n try {\n const s = JSON.stringify(err);\n if (s && s !== \"{}\") return s;\n } catch {\n /* ignore */\n }\n }\n return fallback;\n}\n\n/** Depth-capped walk into `error` / `exception` / `cause`. See above. */\nfunction extractNestedErrorMessage(\n e: Record<string, unknown>,\n depth: number,\n): string | null {\n if (depth > 4) return null;\n for (const key of [\"error\", \"exception\", \"cause\"] as const) {\n const value = e[key];\n if (typeof value === \"string\" && value) return value;\n if (value && typeof value === \"object\") {\n const inner = value as Record<string, unknown>;\n if (typeof inner.message === \"string\" && inner.message) return inner.message;\n if (typeof inner.detail === \"string\" && inner.detail) return inner.detail;\n const deeper = extractNestedErrorMessage(inner, depth + 1);\n if (deeper !== null) return deeper;\n }\n }\n return null;\n}\n\n/**\n * True when a failure is the TRANSPORT dying, not the server answering: the\n * browser is offline, asleep, mid-wifi-handoff, or DNS/TLS failed. It also\n * covers the bounded Envoy/Supabase gateway shape emitted when the upstream\n * database connection dies before response headers. `fetch`\n * rejects with a bare `TypeError: Failed to fetch` in every browser, and\n * supabase-js surfaces that verbatim (message only — no code, no hint).\n *\n * Use it to decide how LOUD a failure is, never whether to handle it. A\n * transport failure carries nothing an engineer can act on, so logging it at\n * error level buries the failures that do — that is how a notes surface filed\n * a system_error every time a laptop slept (2026-08-11). Anything with a\n * Postgres code, an HTTP status, or a server message is NOT this: keep those\n * loud.\n */\nexport function isTransportFailure(err: unknown): boolean {\n if (err instanceof NetworkError || err instanceof OfflineError) return true;\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n if (err instanceof TypeError) return true;\n if (err && typeof err === \"object\") {\n const e = err as { code?: unknown; status?: unknown; message?: unknown; name?: unknown };\n // A real PostgREST/Postgres failure always carries a code or status.\n if (e.code || e.status) return false;\n const text = `${typeof e.name === \"string\" ? e.name : \"\"} ${\n typeof e.message === \"string\" ? e.message : \"\"\n }`.toLowerCase();\n return (\n text.includes(\"failed to fetch\") ||\n text.includes(\"networkerror\") ||\n text.includes(\"network request failed\") ||\n text.includes(\"load failed\") || // Safari's wording for the same condition\n text.includes(\"upstream connect error or disconnect/reset before headers\")\n );\n }\n return false;\n}\n\nexport function toNetError(err: unknown): NetError {\n if (isNetError(err)) return err;\n if (err instanceof DOMException && err.name === \"AbortError\") {\n return new AbortedError(err);\n }\n if (err instanceof TypeError) {\n // fetch() throws TypeError on DNS/connection failure in every browser.\n return new NetworkError(err.message || \"Network request failed\", err);\n }\n return new NetError(\n \"unknown\",\n extractErrorMessage(err),\n { cause: err },\n );\n}\n","/**\n * @ai-matrx/data — the core module.\n *\n * The shared structural vocabulary every lane of the data layer speaks, plus\n * the lane-free net-resilience trio (S12 rides the core — re-exported from\n * `./net`, also importable as `@ai-matrx/data/net`). This entry point\n * otherwise deliberately stays tiny (the split-out law): the Supabase lane is\n * `@ai-matrx/data/db`; the API and files lanes arrive in later minors. Nothing\n * here performs network, storage, or global-state work at import time.\n *\n * The types are STRUCTURAL on purpose: the package never imports\n * `@supabase/supabase-js` — a real `PostgrestError` satisfies\n * `PostgrestLikeError` by shape, and so does any compatible client, so the\n * package works against whatever client the host injects.\n */\n\n/** The shape of a PostgREST error object (plain object, NOT an Error). */\nexport interface PostgrestLikeError {\n message: string;\n details?: string | null;\n hint?: string | null;\n code?: string | null;\n}\n\n/** The shape a `.maybeSingle()` call resolves to. */\nexport interface MaybeSingleResponse<Row> {\n data: Row | null;\n error: PostgrestLikeError | null;\n}\n\n/** A JSON value — the wire-safe subset every column and payload speaks. */\nexport type Json =\n | string\n | number\n | boolean\n | null\n | { [key: string]: Json | undefined }\n | Json[];\n\n/**\n * Converts any thrown value from a Supabase/PostgREST call into a proper Error\n * with a descriptive message. PostgREST errors are plain objects with `message`,\n * `code`, `details`, and `hint` — they are NOT `instanceof Error`, which causes\n * generic catch blocks to lose the real reason.\n */\nexport function pgErrorToError(err: unknown): Error {\n if (err instanceof Error) return err;\n if (err && typeof err === \"object\") {\n const e = err as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof e.message === \"string\" && e.message) parts.push(e.message);\n if (typeof e.details === \"string\" && e.details)\n parts.push(`Details: ${e.details}`);\n if (typeof e.hint === \"string\" && e.hint) parts.push(`Hint: ${e.hint}`);\n if (typeof e.code === \"string\" && e.code) parts.push(`(code: ${e.code})`);\n if (parts.length > 0) return new Error(parts.join(\" — \"));\n }\n return new Error(String(err));\n}\n\nexport * from \"./credentials\";\nexport * from \"./net\";\n","\"use client\";\n\n/**\n * `useRealtimeChannel` — a Supabase Realtime subscription that stays alive.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 18 React modules open a\n * `postgres_changes` channel by hand. Only ONE of them reconnects. The rest\n * share a defect class that is invisible in testing and brutal in production:\n * a channel that drops (laptop sleep, a proxy idling the socket, a Realtime\n * deploy) never comes back, so the screen quietly stops updating and nothing\n * anywhere says so. The user believes stale data.\n *\n * Every hard part lives here:\n *\n * - **Unique topic per mount** (`uniqueChannelTopic`) — React 19\n * double-invokes effects and Fast Refresh re-runs them; a shared topic\n * collides with a still-joined channel and the second subscribe never\n * fires.\n * - **Reconnect with exponential backoff and a stability reset** — a channel\n * that stayed up for a while starts its next backoff from the bottom\n * instead of inheriting an hour-old delay.\n * - **Catch-up on reconnect** (`onReconnect`) — the events that happened\n * while the socket was down are GONE. Reconnecting without a re-read\n * leaves a permanently wrong screen; this hook makes the re-read part of\n * reconnecting rather than something each caller remembers.\n * - **Honest status** — `connecting | connected | reconnecting | disconnected`,\n * so a surface can tell the user it is stale instead of lying silently.\n * - **Latest-callback refs** — a new inline handler on every render must not\n * tear down and rebuild the subscription (the \"why does my realtime\n * reconnect forever\" bug).\n * - **Guaranteed teardown**, including for a channel that is still mid-join.\n *\n * The client type is STRUCTURAL: this package never imports\n * `@supabase/supabase-js`. A real Supabase client satisfies it by shape (there\n * is a compile-time proof of that in `use-realtime-channel.test.tsx`).\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { uniqueChannelTopic } from \"../db/realtime\";\n\nexport type RealtimeStatus =\n | \"connecting\"\n | \"connected\"\n | \"reconnecting\"\n | \"disconnected\";\n\nexport type PostgresChangeEvent = \"*\" | \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\n/** The payload shape `postgres_changes` delivers. */\nexport interface PostgresChangePayload {\n eventType?: string;\n new?: unknown;\n old?: unknown;\n}\n\nexport interface PostgresChangeBinding {\n event: PostgresChangeEvent;\n schema: string;\n table: string;\n /** PostgREST filter, e.g. `file_id=eq.<uuid>`. */\n filter?: string;\n onChange: (payload: PostgresChangePayload) => void;\n}\n\n/** The `postgres_changes` filter, exactly as Supabase declares it. */\nexport interface PostgresChangeFilter {\n event: PostgresChangeEvent;\n schema: string;\n table?: string;\n filter?: string;\n}\n\n/**\n * Structural stand-in for `RealtimeChannel`. The signature must match\n * Supabase's `postgres_changes` overload PRECISELY — a wider `filter` type or\n * a `never`-typed callback makes the real client fail to satisfy this\n * interface, and the host discovers it at its own typecheck. That is exactly\n * what the compile-time proof in `use-realtime-channel.test.tsx` caught during\n * this package's own build.\n */\nexport interface RealtimeChannelLike {\n on(\n type: \"postgres_changes\",\n filter: PostgresChangeFilter,\n callback: (payload: PostgresChangePayload) => void,\n ): RealtimeChannelLike;\n subscribe(callback?: (status: string, error?: Error) => void): unknown;\n}\n\n/** Structural stand-in for `SupabaseClient` (realtime surface only). */\nexport interface RealtimeClientLike {\n channel(topic: string): RealtimeChannelLike;\n removeChannel(channel: RealtimeChannelLike): unknown;\n}\n\nexport interface UseRealtimeChannelOptions {\n /** Turn the subscription off without unmounting. Default `true`. */\n enabled?: boolean;\n /**\n * Called after a DROPPED channel comes back. This is the catch-up door:\n * re-read whatever the missed events would have changed.\n */\n onReconnect?: () => void;\n /** First reconnect delay in ms; doubles up to `maxReconnectDelayMs`. Default `1000`. */\n reconnectDelayMs?: number;\n /** Ceiling for the reconnect backoff. Default `30000`. */\n maxReconnectDelayMs?: number;\n /**\n * How long a channel must stay connected before its backoff resets to the\n * floor. Default `30000` — without it a flapping connection inherits an\n * ever-growing delay and effectively stops trying.\n */\n backoffResetAfterMs?: number;\n}\n\nexport interface RealtimeChannelState {\n status: RealtimeStatus;\n /** True once the channel has dropped at least once this mount. */\n hasReconnected: boolean;\n}\n\nexport function useRealtimeChannel(\n client: RealtimeClientLike | null | undefined,\n topic: string,\n bindings: readonly PostgresChangeBinding[],\n options: UseRealtimeChannelOptions = {},\n): RealtimeChannelState {\n const {\n enabled = true,\n onReconnect,\n reconnectDelayMs = 1_000,\n maxReconnectDelayMs = 30_000,\n backoffResetAfterMs = 30_000,\n } = options;\n\n const [status, setStatus] = useState<RealtimeStatus>(\n enabled && client ? \"connecting\" : \"disconnected\",\n );\n const [hasReconnected, setHasReconnected] = useState(false);\n\n const bindingsRef = useRef(bindings);\n bindingsRef.current = bindings;\n const onReconnectRef = useRef(onReconnect);\n onReconnectRef.current = onReconnect;\n\n // The binding SHAPE (not the handler identities) is what must re-subscribe.\n const shape = bindings\n .map((b) => `${b.event}:${b.schema}.${b.table}:${b.filter ?? \"\"}`)\n .join(\"|\");\n\n useEffect(() => {\n if (!client || !enabled) {\n setStatus(\"disconnected\");\n return undefined;\n }\n\n let disposed = false;\n let attempt = 0;\n let everConnected = false;\n let connectedAt = 0;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let channel: RealtimeChannelLike | null = null;\n\n const teardown = (): void => {\n if (!channel) return;\n const dead = channel;\n channel = null;\n try {\n void client.removeChannel(dead);\n } catch {\n // A channel torn down mid-join can reject; the socket drops it anyway\n // and there is nothing a caller could do about it.\n }\n };\n\n const scheduleReconnect = (): void => {\n if (disposed) return;\n // A channel that held for a while earned a fresh backoff floor.\n if (everConnected && Date.now() - connectedAt >= backoffResetAfterMs) {\n attempt = 0;\n }\n const delay = Math.min(\n reconnectDelayMs * 2 ** attempt,\n maxReconnectDelayMs,\n );\n attempt += 1;\n setStatus(everConnected ? \"reconnecting\" : \"connecting\");\n retryTimer = setTimeout(() => {\n teardown();\n connect();\n }, delay);\n };\n\n const connect = (): void => {\n if (disposed) return;\n let next = client.channel(uniqueChannelTopic(topic));\n for (const [index, binding] of bindingsRef.current.entries()) {\n next = next.on(\n \"postgres_changes\",\n {\n event: binding.event,\n schema: binding.schema,\n table: binding.table,\n ...(binding.filter !== undefined ? { filter: binding.filter } : {}),\n },\n (payload: PostgresChangePayload) => {\n // Read through the ref so a re-rendered inline handler is used\n // without rebuilding the subscription.\n bindingsRef.current[index]?.onChange(payload);\n },\n );\n }\n channel = next;\n next.subscribe((subscribeStatus) => {\n if (disposed) return;\n if (subscribeStatus === \"SUBSCRIBED\") {\n const rejoined = everConnected;\n everConnected = true;\n connectedAt = Date.now();\n attempt = 0;\n setStatus(\"connected\");\n if (rejoined) {\n setHasReconnected(true);\n onReconnectRef.current?.();\n }\n return;\n }\n if (\n subscribeStatus === \"CHANNEL_ERROR\" ||\n subscribeStatus === \"TIMED_OUT\" ||\n subscribeStatus === \"CLOSED\"\n ) {\n scheduleReconnect();\n }\n });\n };\n\n connect();\n\n return () => {\n disposed = true;\n if (retryTimer !== undefined) clearTimeout(retryTimer);\n teardown();\n };\n }, [\n client,\n enabled,\n topic,\n shape,\n reconnectDelayMs,\n maxReconnectDelayMs,\n backoffResetAfterMs,\n ]);\n\n return { status, hasReconnected };\n}\n","/**\n * Realtime channel hygiene — RE-EXPORT ONLY. The canonical home is\n * `@ai-matrx/realtime`.\n *\n * Channel NAMING belongs to the realtime package (its work order, R1): it owns\n * the channel-naming registry, and `uniqueChannelTopic` is the React-19\n * double-invoked-effect collision guard that registry hands out. This package\n * kept the only copy while `@ai-matrx/realtime` was unpublished; that copy is\n * now DELETED and this module forwards, so the two can never drift and the\n * `#`-suffixed instance topics a Matrx app opens all come from one counter.\n *\n * Why forwarding matters and a second copy would not do: the old\n * implementation kept its sequence in a module-level `let`. With unsplit dual\n * ESM/CJS output a module can be instantiated twice in one process, so two\n * counters both starting at 1 hand out the same \"unique\" topic twice — exactly\n * the collision the function exists to prevent. The realtime package keeps the\n * counter in a `globalThis` slot (`Symbol.for`) and proves it across module\n * graphs in its packed-tarball canary.\n *\n * `@ai-matrx/realtime` is the deeper layer (zero runtime dependencies; it does\n * not and must not depend on `@ai-matrx/data`), so this direction keeps the\n * sibling graph acyclic — `node scripts/check_ts_sibling_graph.mjs`.\n *\n * New realtime work should not reach for this at all: declare a channel\n * namespace with `defineChannelNamespace` and let the manager assign the\n * instance topic. This export exists for the call sites that have not yet\n * migrated off hand-rolled `.channel(` blocks.\n */\n\nexport { uniqueChannelTopic } from \"@ai-matrx/realtime\";\n","\"use client\";\n\n/**\n * `useRealtimeQuery` — a read that stays current, in one call.\n *\n * This is the exact shape 8+ matrx-frontend hooks hand-assemble: load a list,\n * subscribe to the rows behind it, refresh on change. Composing the two\n * primitives here (rather than in each host hook) closes the gap none of the\n * hand-rolled copies close — a change that arrives while the socket is down is\n * never delivered, so reconnecting MUST re-read. `useRealtimeChannel`'s\n * `onReconnect` is wired to `refresh` for you.\n *\n * Realtime events are COALESCED: a bulk write firing 200 INSERTs must not\n * produce 200 reads. Changes inside `debounceMs` collapse into one refresh.\n */\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport {\n useAsyncData,\n type AsyncData,\n type AsyncDataContext,\n type UseAsyncDataOptions,\n} from \"./use-async-data\";\nimport {\n useRealtimeChannel,\n type PostgresChangeBinding,\n type PostgresChangeEvent,\n type RealtimeClientLike,\n type RealtimeStatus,\n type UseRealtimeChannelOptions,\n} from \"./use-realtime-channel\";\n\n/** A table to watch. `onChange` is optional — refreshing is the default. */\nexport interface WatchedTable {\n event?: PostgresChangeEvent;\n schema: string;\n table: string;\n filter?: string;\n}\n\nexport interface UseRealtimeQueryOptions<T>\n extends UseAsyncDataOptions<T>,\n Pick<\n UseRealtimeChannelOptions,\n \"reconnectDelayMs\" | \"maxReconnectDelayMs\" | \"backoffResetAfterMs\"\n > {\n /** Coalesce bursts of realtime events into one refresh. Default `120`ms. */\n debounceMs?: number;\n}\n\nexport interface RealtimeQuery<T> extends AsyncData<T> {\n realtimeStatus: RealtimeStatus;\n}\n\nexport function useRealtimeQuery<T>(\n args: {\n client: RealtimeClientLike | null | undefined;\n topic: string;\n watch: readonly WatchedTable[];\n load: (context: AsyncDataContext) => Promise<T>;\n deps: readonly unknown[];\n },\n options: UseRealtimeQueryOptions<T> = {},\n): RealtimeQuery<T> {\n const {\n debounceMs = 120,\n reconnectDelayMs,\n maxReconnectDelayMs,\n backoffResetAfterMs,\n ...dataOptions\n } = options;\n const query = useAsyncData(args.load, args.deps, dataOptions);\n\n const refreshRef = useRef(query.refresh);\n refreshRef.current = query.refresh;\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(\n () => () => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n },\n [],\n );\n\n const shape = args.watch\n .map((w) => `${w.event ?? \"*\"}:${w.schema}.${w.table}:${w.filter ?? \"\"}`)\n .join(\"|\");\n\n const bindings = useMemo<PostgresChangeBinding[]>(() => {\n const coalescedRefresh = (): void => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => {\n timerRef.current = undefined;\n refreshRef.current();\n }, debounceMs);\n };\n return args.watch.map((w) => ({\n event: w.event ?? \"*\",\n schema: w.schema,\n table: w.table,\n ...(w.filter !== undefined ? { filter: w.filter } : {}),\n onChange: coalescedRefresh,\n }));\n // `shape` is the stable identity of `args.watch`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shape, debounceMs]);\n\n const { status } = useRealtimeChannel(args.client, args.topic, bindings, {\n enabled: dataOptions.enabled ?? true,\n ...(reconnectDelayMs !== undefined ? { reconnectDelayMs } : {}),\n ...(maxReconnectDelayMs !== undefined ? { maxReconnectDelayMs } : {}),\n ...(backoffResetAfterMs !== undefined ? { backoffResetAfterMs } : {}),\n // The events lost while the socket was down are unrecoverable; the read is\n // the only way back to truth.\n onReconnect: () => refreshRef.current(),\n });\n\n return { ...query, realtimeStatus: status };\n}\n","\"use client\";\n\n/**\n * `useAsyncAction` — a write with honest pending/error state.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 554 components hold their own\n * `saving`/`submitting`/`deleting` boolean around an async call. The repeated\n * defects:\n *\n * 1. **No double-submit guard.** A disabled button is not one — a fast second\n * click, an Enter key, or a re-render race all get through, and the write\n * runs twice. Here a second `run` while one is in flight is REFUSED and\n * says so (`{ ok: false, error: ActionInFlightError }`) instead of quietly\n * duplicating a row.\n * 2. **`setState` after unmount**, because `finally { setSaving(false) }`\n * does not know the component is gone.\n * 3. **`String(err)` again** — a PostgREST error object renders as\n * \"[object Object]\". Every failure here goes through `pgErrorToError`.\n * 4. **The error is thrown AND stored, or neither.** This hook returns a\n * discriminated result so a caller can branch without try/catch, while\n * `error` holds the same `Error` for rendering. Nothing is swallowed:\n * the failure is in the return value, in `error`, and in `onError`.\n *\n * Writes are NEVER retried here — that is the whole point of the boundary. A\n * user-authored submission replayed after an ambiguous timeout is how you get\n * two invoices. Retry belongs on idempotent reads (`useAsyncData`).\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\n\n/** Thrown-shaped refusal when a second run overlaps an in-flight one. */\nexport class ActionInFlightError extends Error {\n constructor() {\n super(\n \"This action is already running. The duplicate submission was refused — \" +\n \"wait for the in-flight action to finish.\",\n );\n this.name = \"ActionInFlightError\";\n }\n}\n\nexport type ActionResult<R> =\n | { ok: true; value: R }\n | { ok: false; error: Error };\n\nexport interface UseAsyncActionOptions<R> {\n onSuccess?: (value: R) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface AsyncAction<Args extends readonly unknown[], R> {\n run: (...args: Args) => Promise<ActionResult<R>>;\n pending: boolean;\n /** The last failure, as a real `Error`. Cleared when a new run starts. */\n error: Error | null;\n /** Clear `error` without running anything. */\n reset: () => void;\n}\n\nexport function useAsyncAction<Args extends readonly unknown[], R>(\n action: (...args: Args) => Promise<R>,\n options: UseAsyncActionOptions<R> = {},\n): AsyncAction<Args, R> {\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const actionRef = useRef(action);\n actionRef.current = action;\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const inFlightRef = useRef(false);\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const run = useCallback(async (...args: Args): Promise<ActionResult<R>> => {\n if (inFlightRef.current) {\n const refusal = new ActionInFlightError();\n if (mountedRef.current) setError(refusal);\n optionsRef.current.onError?.(refusal);\n return { ok: false, error: refusal };\n }\n inFlightRef.current = true;\n if (mountedRef.current) {\n setPending(true);\n setError(null);\n }\n try {\n const value = await actionRef.current(...args);\n if (mountedRef.current) setPending(false);\n optionsRef.current.onSuccess?.(value);\n return { ok: true, value };\n } catch (raw) {\n const failure = pgErrorToError(raw);\n if (mountedRef.current) {\n setPending(false);\n setError(failure);\n }\n optionsRef.current.onError?.(failure);\n return { ok: false, error: failure };\n } finally {\n inFlightRef.current = false;\n }\n }, []);\n\n const reset = useCallback(() => setError(null), []);\n\n return { run, pending, error, reset };\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\n\nexport interface PaginatedPage<T, C extends string | number> {\n rows: T[];\n nextCursor: C | null;\n totalItems?: number;\n}\n\nexport interface PaginatedDataOptions<T, C extends string | number> {\n /** Immutable identity of every input that changes the source result. */\n queryKey: string;\n initialCursor: C;\n loadPage: (context: { cursor: C; signal: AbortSignal }) => Promise<PaginatedPage<T, C>>;\n getRowId: (row: T) => string;\n enabled?: boolean;\n onError?: (error: Error) => void;\n}\n\nexport interface PaginatedData<T> {\n queryKey: string;\n rows: T[];\n loading: boolean;\n isFetchingNextPage: boolean;\n error: Error | null;\n hasNextPage: boolean;\n loadNextPage: () => Promise<void>;\n refresh: () => void;\n totalItems: number | undefined;\n isLoadingAll: boolean;\n requestLoadAll: () => void;\n stopLoadingAll: () => void;\n}\n\ntype Phase = \"idle\" | \"initial\" | \"ready\" | \"next\" | \"initial-error\" | \"next-error\" | \"cursor-error\";\n\ninterface Snapshot<T, C extends string | number> {\n queryKey: string | null;\n initialCursor: C | null;\n rows: T[];\n nextCursor: C | null;\n totalItems: number | undefined;\n error: Error | null;\n phase: Phase;\n}\n\nfunction isAbort(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === \"AbortError\";\n}\n\nfunction mergeRows<T>(current: readonly T[], incoming: readonly T[], getRowId: (row: T) => string): T[] {\n const next = [...current];\n const indexById = new Map<string, number>();\n current.forEach((row, index) => indexById.set(getRowId(row), index));\n for (const row of incoming) {\n const id = getRowId(row);\n const existing = indexById.get(id);\n if (existing === undefined) {\n indexById.set(id, next.length);\n next.push(row);\n } else {\n // The server's newer representation wins without moving visible rows.\n next[existing] = row;\n }\n }\n return next;\n}\n\n/** Cursor accumulation with no cache and no table dependency. */\nexport function usePaginatedData<T, C extends string | number>(\n options: PaginatedDataOptions<T, C>,\n): PaginatedData<T> {\n const { queryKey, initialCursor, enabled = true } = options;\n const [refreshTick, setRefreshTick] = useState(0);\n const [loadAllRequested, setLoadAllRequested] = useState(false);\n const [snapshot, setSnapshot] = useState<Snapshot<T, C>>({\n queryKey: null, initialCursor: null, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"idle\",\n });\n const snapshotRef = useRef(snapshot);\n const queryKeyRef = useRef(queryKey);\n queryKeyRef.current = queryKey;\n const initialCursorRef = useRef(initialCursor);\n initialCursorRef.current = initialCursor;\n const enabledRef = useRef(enabled);\n enabledRef.current = enabled;\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const generationRef = useRef(0);\n const inFlightRef = useRef(false);\n const controllerRef = useRef<AbortController | null>(null);\n const receiptsRef = useRef(new Set<C>());\n\n const publish = useCallback((next: Snapshot<T, C>) => {\n snapshotRef.current = next;\n setSnapshot(next);\n }, []);\n const isCurrent = useCallback((requestKey: string, requestInitialCursor: C, generation: number, signal: AbortSignal) => (\n !signal.aborted && generation === generationRef.current && requestKey === queryKeyRef.current && requestInitialCursor === initialCursorRef.current && enabledRef.current\n ), []);\n\n const requestPage = useCallback(async (\n cursor: C, kind: \"initial\" | \"next\", requestKey: string, requestInitialCursor: C, generation: number,\n ): Promise<void> => {\n if (inFlightRef.current || !enabledRef.current || requestKey !== queryKeyRef.current) return;\n const controller = new AbortController();\n controllerRef.current = controller;\n inFlightRef.current = true;\n publish({ ...snapshotRef.current, queryKey: requestKey, initialCursor: requestInitialCursor, error: null, phase: kind });\n try {\n const page = await optionsRef.current.loadPage({ cursor, signal: controller.signal });\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n const rows = mergeRows(\n kind === \"initial\" ? [] : snapshotRef.current.rows,\n page.rows,\n optionsRef.current.getRowId,\n );\n receiptsRef.current.add(cursor);\n const repeatedCursor = page.nextCursor !== null && receiptsRef.current.has(page.nextCursor);\n const error = repeatedCursor\n ? new Error(`Pagination source returned cursor \"${String(page.nextCursor)}\" more than once; loading stopped to prevent a duplicate-page loop.`)\n : null;\n // Publish after clearing the one-flight latch: load-all's effect observes\n // this terminal page state and may immediately schedule exactly one more.\n inFlightRef.current = false;\n controllerRef.current = null;\n publish({\n queryKey: requestKey,\n initialCursor: requestInitialCursor,\n rows,\n nextCursor: error ? null : page.nextCursor,\n totalItems: page.totalItems ?? snapshotRef.current.totalItems,\n error,\n phase: error ? \"cursor-error\" : \"ready\",\n });\n if (error) optionsRef.current.onError?.(error);\n if (error) setLoadAllRequested(false);\n } catch (raw) {\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal) || isAbort(raw)) return;\n const error = pgErrorToError(raw);\n publish({ ...snapshotRef.current, queryKey: requestKey, initialCursor: requestInitialCursor, error, phase: kind === \"initial\" ? \"initial-error\" : \"next-error\" });\n setLoadAllRequested(false);\n optionsRef.current.onError?.(error);\n } finally {\n if (isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) {\n inFlightRef.current = false;\n controllerRef.current = null;\n }\n }\n }, [isCurrent, publish]);\n\n useEffect(() => {\n generationRef.current += 1;\n const generation = generationRef.current;\n controllerRef.current?.abort();\n controllerRef.current = null;\n inFlightRef.current = false;\n receiptsRef.current = new Set<C>();\n setLoadAllRequested(false);\n if (!enabled) {\n publish({ queryKey, initialCursor, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"idle\" });\n return undefined;\n }\n // A former auth/org/filter identity must not remain visible for one paint.\n publish({ queryKey, initialCursor, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"initial\" });\n void requestPage(initialCursor, \"initial\", queryKey, initialCursor, generation);\n return () => controllerRef.current?.abort();\n }, [enabled, initialCursor, publish, queryKey, refreshTick, requestPage]);\n\n const loadNextPage = useCallback(async (): Promise<void> => {\n const current = snapshotRef.current;\n const requestKey = queryKeyRef.current;\n if (!enabledRef.current || inFlightRef.current || current.queryKey !== requestKey || current.initialCursor !== initialCursorRef.current) return;\n const generation = generationRef.current;\n if (current.phase === \"initial-error\") {\n receiptsRef.current = new Set<C>();\n await requestPage(optionsRef.current.initialCursor, \"initial\", requestKey, optionsRef.current.initialCursor, generation);\n return;\n }\n if (current.phase === \"cursor-error\" || current.nextCursor === null) return;\n if (receiptsRef.current.has(current.nextCursor)) {\n const error = new Error(`Pagination source tried to load cursor \"${String(current.nextCursor)}\" twice; loading stopped to prevent a duplicate-page loop.`);\n publish({ ...current, error, nextCursor: null, phase: \"cursor-error\" });\n optionsRef.current.onError?.(error);\n return;\n }\n await requestPage(current.nextCursor, \"next\", requestKey, current.initialCursor ?? optionsRef.current.initialCursor, generation);\n }, [publish, requestPage]);\n\n const refresh = useCallback(() => setRefreshTick((tick) => tick + 1), []);\n const requestLoadAll = useCallback(() => setLoadAllRequested(true), []);\n const stopLoadingAll = useCallback(() => setLoadAllRequested(false), []);\n useEffect(() => {\n if (!loadAllRequested || inFlightRef.current) return;\n const current = snapshotRef.current;\n if (current.phase === \"ready\" && current.nextCursor !== null) void loadNextPage();\n else if (current.phase === \"cursor-error\" || current.nextCursor === null) setLoadAllRequested(false);\n }, [loadAllRequested, loadNextPage, snapshot]);\n const visible = enabled && snapshot.queryKey === queryKey && snapshot.initialCursor === initialCursor;\n const phase = visible ? snapshot.phase : \"initial\";\n return {\n queryKey,\n rows: visible ? snapshot.rows : [],\n loading: enabled && phase === \"initial\",\n isFetchingNextPage: visible && phase === \"next\",\n error: visible ? snapshot.error : null,\n hasNextPage: visible && phase !== \"cursor-error\" && (snapshot.nextCursor !== null || phase === \"initial-error\"),\n loadNextPage,\n refresh,\n totalItems: visible ? snapshot.totalItems : undefined,\n isLoadingAll: visible && loadAllRequested,\n requestLoadAll,\n stopLoadingAll,\n };\n}\n","/** Framework-free validation shared by every host that resolves tables.pagination. */\nexport type ScrollPaginationPolicy =\n | { mode: \"scroll\"; thresholdPx: number; intentTimeoutMs: number; valid: true }\n /** `approvedBy` is recorded attribution from the configured policy, not an authentication assertion. */\n | { mode: \"manual\"; reason: string; approvedBy: string; valid: true }\n | { mode: \"suspended\"; reason: string; valid: false };\n\nexport function resolveScrollPaginationPolicy(values: {\n mode: unknown;\n thresholdPx: unknown;\n intentTimeoutMs: unknown;\n reason?: unknown;\n approvedBy?: unknown;\n}): ScrollPaginationPolicy {\n const thresholdPx = values.thresholdPx;\n const intentTimeoutMs = values.intentTimeoutMs;\n const dimensionsValid = typeof thresholdPx === \"number\" && Number.isFinite(thresholdPx) && thresholdPx >= 0 &&\n typeof intentTimeoutMs === \"number\" && Number.isFinite(intentTimeoutMs) && intentTimeoutMs > 0;\n if (values.mode === \"scroll\") {\n return dimensionsValid\n ? { mode: \"scroll\", thresholdPx, intentTimeoutMs, valid: true }\n : suspendScrollPaginationPolicy(\"Automatic loading is paused because its scrolling preferences are missing or invalid.\");\n }\n if (values.mode === \"manual\") {\n const reason = nonBlankString(values.reason);\n const approvedBy = nonBlankString(values.approvedBy);\n return reason && approvedBy\n ? { mode: \"manual\", reason, approvedBy, valid: true }\n : suspendScrollPaginationPolicy(\"Automatic loading is paused because the manual exception is missing its reason or approving owner.\");\n }\n return suspendScrollPaginationPolicy(\"Automatic loading is paused because its pagination mode is missing or invalid.\");\n}\n\n/** A temporary configuration state keeps explicit Load more usable without inventing an approval. */\nexport function suspendScrollPaginationPolicy(reason: string): ScrollPaginationPolicy {\n return { mode: \"suspended\", reason, valid: false };\n}\n\nfunction nonBlankString(value: unknown): string | null {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : null;\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","useState","useRef","useEffect","useEffect","useRef","useRef","useEffect","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","useCallback","useEffect","useRef","useState","isAbort","useState","useRef","useCallback","useEffect"]}
1
+ {"version":3,"sources":["../src/react/use-async-data.ts","../src/net/errors.ts","../src/index.ts","../src/react/use-realtime-channel.ts","../src/db/realtime.ts","../src/react/use-realtime-query.ts","../src/react/use-async-action.ts","../src/react/use-paginated-data.ts","../src/pagination/scroll-policy.ts"],"sourcesContent":["\"use client\";\n\n/**\n * `useAsyncData` — the one honest way to read data into a React component.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 93 files hand-wire the exact\n * same five pieces — a `cancelled` flag, a `loading` boolean, an `error`\n * string, a refetch tick, and a `.catch()`. Each copy re-derives the same\n * mistakes, and the census found them all in the wild:\n *\n * 1. `setError(String(err))` — a PostgREST error is a PLAIN OBJECT, not an\n * `Error`, so `String(err)` renders \"[object Object]\" to the user and the\n * real `message`/`code`/`hint` are lost. This hook runs every failure\n * through `pgErrorToError`.\n * 2. No `AbortSignal` reaches the loader, so a superseded request keeps\n * running to completion, burning a connection and (worse) still counting\n * against rate limits. This hook aborts the previous run.\n * 3. Stale-response ordering — nothing stops a slow first request from\n * overwriting a fast second one. This hook stamps every run and ignores\n * any result that is not the newest.\n * 4. `loading` flips true on every key change, blanking the screen even when\n * previous data is still perfectly displayable. `keepPreviousData` is ON\n * by default and `refreshing` reports the background load honestly.\n * 5. An abort is treated as a failure and painted as an error banner. Here an\n * abort is silent by construction — it is not a failure, it is a\n * cancellation the component itself asked for.\n * 6. No retry, so one transient network blip is a permanent empty state.\n * Transient TRANSPORT failures retry with backoff; a server that answered\n * (a 400, an RLS denial) never does — retrying those is how you turn one\n * bug into a storm.\n *\n * Nothing fails silently: a terminal failure always lands in `error`, and\n * `onError` fires with the same real `Error`.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\nimport { isNetError, toNetError } from \"../net/errors\";\n\nexport interface UseAsyncDataOptions<T> {\n /**\n * Skip loading entirely (e.g. the key is null). Default `true`.\n * While disabled: `loading` is false and existing data is retained.\n */\n enabled?: boolean;\n /** Shown before the first load resolves. */\n initialData?: T;\n /**\n * Keep the previous key's data visible while the new key loads.\n * Default `true` — a list that blanks on every filter change is a worse\n * experience than a list that is briefly one keystroke stale.\n */\n keepPreviousData?: boolean;\n /**\n * How many times to retry a TRANSIENT TRANSPORT failure. Default `2`.\n * Errors the server actually answered with are never retried.\n */\n retry?: number;\n /** First backoff delay in ms; doubles per attempt. Default `300`. */\n retryDelayMs?: number;\n /** Called once per terminal failure, with the real `Error`. */\n onError?: (error: Error) => void;\n}\n\nexport interface AsyncData<T> {\n /** `undefined` until the first load resolves (or `initialData`). */\n data: T | undefined;\n /** True only while there is nothing to show yet. */\n loading: boolean;\n /** True while a load runs over data that is already on screen. */\n refreshing: boolean;\n /** The last terminal failure, as a real `Error`. Cleared on success. */\n error: Error | null;\n /** Re-run the loader now. Safe to call from an event handler or effect. */\n refresh: () => void;\n}\n\nexport interface AsyncDataContext {\n signal: AbortSignal;\n}\n\nfunction isAbort(error: unknown): boolean {\n if (error instanceof DOMException && error.name === \"AbortError\") return true;\n if (error instanceof Error && error.name === \"AbortError\") return true;\n return false;\n}\n\n/** Only a transport that never got an answer is worth retrying. */\nfunction isRetryable(error: unknown): boolean {\n const net = isNetError(error) ? error : toNetError(error);\n return net.retryable;\n}\n\nconst sleep = (ms: number, signal: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n function onAbort(): void {\n clearTimeout(timer);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n\nexport function useAsyncData<T>(\n load: (context: AsyncDataContext) => Promise<T>,\n deps: readonly unknown[],\n options: UseAsyncDataOptions<T> = {},\n): AsyncData<T> {\n const {\n enabled = true,\n initialData,\n keepPreviousData = true,\n retry = 2,\n retryDelayMs = 300,\n onError,\n } = options;\n\n const [data, setData] = useState<T | undefined>(initialData);\n const [loading, setLoading] = useState<boolean>(enabled);\n const [refreshing, setRefreshing] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n const [tick, setTick] = useState(0);\n\n // Latest-value refs so a changed callback identity never re-triggers a load\n // (the host runs the React Compiler; the package must be correct without it).\n const loadRef = useRef(load);\n loadRef.current = load;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n const runIdRef = useRef(0);\n const hasDataRef = useRef(initialData !== undefined);\n\n const refresh = useCallback(() => setTick((n) => n + 1), []);\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n setRefreshing(false);\n return undefined;\n }\n\n const runId = ++runIdRef.current;\n const controller = new AbortController();\n const showingData = hasDataRef.current && keepPreviousData;\n\n if (!keepPreviousData) {\n hasDataRef.current = false;\n setData(undefined);\n }\n setLoading(!showingData);\n setRefreshing(showingData);\n\n void (async () => {\n let attempt = 0;\n for (;;) {\n try {\n const result = await loadRef.current({ signal: controller.signal });\n if (runId !== runIdRef.current || controller.signal.aborted) return;\n hasDataRef.current = true;\n setData(result);\n setError(null);\n setLoading(false);\n setRefreshing(false);\n return;\n } catch (raw) {\n if (isAbort(raw) || controller.signal.aborted) return;\n if (runId !== runIdRef.current) return;\n if (attempt < retry && isRetryable(raw)) {\n attempt += 1;\n try {\n await sleep(retryDelayMs * 2 ** (attempt - 1), controller.signal);\n } catch {\n return;\n }\n continue;\n }\n const failure = pgErrorToError(raw);\n setError(failure);\n setLoading(false);\n setRefreshing(false);\n onErrorRef.current?.(failure);\n return;\n }\n }\n })();\n\n return () => {\n controller.abort();\n };\n // `deps` is the caller's declared key set — the whole point of the hook.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, keepPreviousData, retry, retryDelayMs, tick, ...deps]);\n\n return { data, loading, refreshing, error, refresh };\n}\n","/**\n * Structured errors for the resilience layer.\n *\n * Every network-layer failure resolves to one of these classes so callers\n * (and any recovery UI) can branch on error type, not on string matching.\n *\n * Ported verbatim from matrx-frontend `lib/net/errors.ts` — the origin of the\n * trio. The one coupling seam removed: `extractErrorMessage` (previously the\n * app's `utils/errors`) is pure logic and ships inside the package.\n */\n\nexport type NetErrorCode =\n | \"connect-timeout\"\n | \"total-timeout\"\n | \"heartbeat-timeout\"\n | \"network\"\n | \"http\"\n | \"aborted\"\n | \"offline\"\n | \"unknown\";\n\nexport class NetError extends Error {\n readonly code: NetErrorCode;\n override readonly cause?: unknown;\n readonly status?: number;\n readonly retryable: boolean;\n\n constructor(\n code: NetErrorCode,\n message: string,\n opts: { cause?: unknown; status?: number; retryable?: boolean } = {},\n ) {\n super(message);\n this.name = \"NetError\";\n this.code = code;\n this.cause = opts.cause;\n // Conditional only for `exactOptionalPropertyTypes` — same runtime shape.\n if (opts.status !== undefined) this.status = opts.status;\n this.retryable = opts.retryable ?? false;\n }\n}\n\nexport class ConnectTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\"connect-timeout\", `Connection timed out after ${ms}ms`, {\n cause,\n retryable: true,\n });\n this.name = \"ConnectTimeoutError\";\n }\n}\n\nexport class TotalTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\"total-timeout\", `Request exceeded ${ms}ms total budget`, {\n cause,\n retryable: false,\n });\n this.name = \"TotalTimeoutError\";\n }\n}\n\nexport class HeartbeatTimeoutError extends NetError {\n constructor(ms: number, cause?: unknown) {\n super(\n \"heartbeat-timeout\",\n `No server activity for ${ms}ms — stream considered dead`,\n { cause, retryable: false },\n );\n this.name = \"HeartbeatTimeoutError\";\n }\n}\n\nexport class NetworkError extends NetError {\n constructor(message: string, cause?: unknown) {\n super(\"network\", message, { cause, retryable: true });\n this.name = \"NetworkError\";\n }\n}\n\nexport class HttpError extends NetError {\n constructor(status: number, message: string, cause?: unknown) {\n // 408/429/5xx are generally safe to retry for idempotent ops.\n const retryable = status === 408 || status === 429 || status >= 500;\n super(\"http\", message, { cause, status, retryable });\n this.name = \"HttpError\";\n }\n}\n\nexport class AbortedError extends NetError {\n constructor(cause?: unknown) {\n super(\"aborted\", \"Request was aborted\", { cause, retryable: false });\n this.name = \"AbortedError\";\n }\n}\n\nexport class OfflineError extends NetError {\n constructor() {\n super(\"offline\", \"Browser is offline\", { retryable: true });\n this.name = \"OfflineError\";\n }\n}\n\nexport function isNetError(err: unknown): err is NetError {\n return err instanceof NetError;\n}\n\n/**\n * Folds any thrown value into a descriptive message string — THE one reader\n * for every failure shape this platform actually emits.\n *\n * 2026-09-07 (duplication census rows 9, 10, 21): this body had DRIFTED BEHIND\n * three host copies, which is the campaign's failure mode running in the\n * package's favour-direction. Each host had learned something the package\n * never did, so every non-frontend consumer of `@ai-matrx/data/net` was\n * flattening a server's own sentence into \"An unexpected error occurred\". All\n * three are folded in here, with a named regression test per shape:\n *\n * 1. **FastAPI's `detail`** (matrx-frontend `utils/errors.ts`, learned\n * 2026-08-31 as V2 finding G5). Our server refuses in `detail` — a string,\n * or an object carrying `message` — and every reader checked\n * `message`/`details` only. The server's sentence is the whole point of\n * catching this.\n * 2. **A `fallback` argument** (matrx-frontend `ShortcutForm.tsx`). A caller\n * who knows what it was doing can say \"Failed to save shortcut\" instead of\n * the generic sentence. Optional; the default is unchanged, so every\n * existing single-argument call behaves exactly as before.\n * 3. **Recursion into nested error objects** (aidream `workflow-studio`, two\n * byte-identical copies). The run pipeline nests: `{error: {type, message}}`\n * and `{error: {error: ...}}` and `{exception: {...}}` are all real shapes,\n * and a non-recursive reader answers \"An unexpected error occurred\" while\n * holding the actual reason two levels down. Depth-capped at 4 — a cyclic\n * or absurdly deep object must not hang a render.\n *\n * PostgREST-style errors are plain objects carrying\n * `message`/`details`/`hint`/`code`; a bare `String(err)` on those yields\n * \"[object Object]\", which is why this exists at all.\n */\nexport function extractErrorMessage(\n err: unknown,\n fallback = \"An unexpected error occurred\",\n): string {\n if (!err) return \"Unknown error\";\n if (typeof err === \"string\") return err;\n if (err instanceof Error) return err.message;\n if (typeof err === \"object\") {\n const e = err as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof e.message === \"string\" && e.message) parts.push(e.message);\n // FastAPI's own word for the reason — only when nothing said `message`,\n // so a PostgREST error keeps its exact composed sentence.\n if (parts.length === 0) {\n if (typeof e.detail === \"string\" && e.detail) parts.push(e.detail);\n else if (e.detail && typeof e.detail === \"object\") {\n const d = e.detail as Record<string, unknown>;\n if (typeof d.message === \"string\" && d.message) parts.push(d.message);\n }\n }\n if (typeof e.details === \"string\" && e.details) parts.push(e.details);\n if (typeof e.hint === \"string\" && e.hint) parts.push(`Hint: ${e.hint}`);\n if (typeof e.code === \"string\" && e.code) parts.push(`Code: ${e.code}`);\n if (parts.length > 0) return parts.join(\" · \");\n\n const nested = extractNestedErrorMessage(e, 0);\n if (nested !== null) return nested;\n\n try {\n const s = JSON.stringify(err);\n if (s && s !== \"{}\") return s;\n } catch {\n /* ignore */\n }\n }\n return fallback;\n}\n\n/** Depth-capped walk into `error` / `exception` / `cause`. See above. */\nfunction extractNestedErrorMessage(\n e: Record<string, unknown>,\n depth: number,\n): string | null {\n if (depth > 4) return null;\n for (const key of [\"error\", \"exception\", \"cause\"] as const) {\n const value = e[key];\n if (typeof value === \"string\" && value) return value;\n if (value && typeof value === \"object\") {\n const inner = value as Record<string, unknown>;\n if (typeof inner.message === \"string\" && inner.message) return inner.message;\n if (typeof inner.detail === \"string\" && inner.detail) return inner.detail;\n const deeper = extractNestedErrorMessage(inner, depth + 1);\n if (deeper !== null) return deeper;\n }\n }\n return null;\n}\n\n/**\n * True when a failure is the TRANSPORT dying, not the server answering: the\n * browser is offline, asleep, mid-wifi-handoff, or DNS/TLS failed. It also\n * covers the bounded Envoy/Supabase gateway shape emitted when the upstream\n * database connection dies before response headers. `fetch`\n * rejects with a bare `TypeError: Failed to fetch` in every browser, and\n * supabase-js surfaces that verbatim (message only — no code, no hint).\n *\n * Use it to decide how LOUD a failure is, never whether to handle it. A\n * transport failure carries nothing an engineer can act on, so logging it at\n * error level buries the failures that do — that is how a notes surface filed\n * a system_error every time a laptop slept (2026-08-11). Anything with a\n * Postgres code, an HTTP status, or a server message is NOT this: keep those\n * loud.\n */\nexport function isTransportFailure(err: unknown): boolean {\n if (err instanceof NetworkError || err instanceof OfflineError) return true;\n if (typeof navigator !== \"undefined\" && navigator.onLine === false) return true;\n if (err instanceof TypeError) return true;\n if (err && typeof err === \"object\") {\n const e = err as { code?: unknown; status?: unknown; message?: unknown; name?: unknown };\n // A real PostgREST/Postgres failure always carries a code or status.\n if (e.code || e.status) return false;\n const text = `${typeof e.name === \"string\" ? e.name : \"\"} ${\n typeof e.message === \"string\" ? e.message : \"\"\n }`.toLowerCase();\n return (\n text.includes(\"failed to fetch\") ||\n text.includes(\"networkerror\") ||\n text.includes(\"network request failed\") ||\n text.includes(\"load failed\") || // Safari's wording for the same condition\n text.includes(\"upstream connect error or disconnect/reset before headers\")\n );\n }\n return false;\n}\n\nexport function toNetError(err: unknown): NetError {\n if (isNetError(err)) return err;\n if (err instanceof DOMException && err.name === \"AbortError\") {\n return new AbortedError(err);\n }\n if (err instanceof TypeError) {\n // fetch() throws TypeError on DNS/connection failure in every browser.\n return new NetworkError(err.message || \"Network request failed\", err);\n }\n return new NetError(\n \"unknown\",\n extractErrorMessage(err),\n { cause: err },\n );\n}\n","/**\n * @ai-matrx/data — the core module.\n *\n * The shared structural vocabulary every lane of the data layer speaks, plus\n * the lane-free net-resilience trio (S12 rides the core — re-exported from\n * `./net`, also importable as `@ai-matrx/data/net`). This entry point\n * otherwise deliberately stays tiny (the split-out law): the Supabase lane is\n * `@ai-matrx/data/db`; the API and files lanes arrive in later minors. Nothing\n * here performs network, storage, or global-state work at import time.\n *\n * The types are STRUCTURAL on purpose: the package never imports\n * `@supabase/supabase-js` — a real `PostgrestError` satisfies\n * `PostgrestLikeError` by shape, and so does any compatible client, so the\n * package works against whatever client the host injects.\n */\n\n/** The shape of a PostgREST error object (plain object, NOT an Error). */\nexport interface PostgrestLikeError {\n message: string;\n details?: string | null;\n hint?: string | null;\n code?: string | null;\n}\n\n/** The shape a `.maybeSingle()` call resolves to. */\nexport interface MaybeSingleResponse<Row> {\n data: Row | null;\n error: PostgrestLikeError | null;\n}\n\n/** A JSON value — the wire-safe subset every column and payload speaks. */\nexport type Json =\n | string\n | number\n | boolean\n | null\n | { [key: string]: Json | undefined }\n | Json[];\n\n/**\n * Converts any thrown value from a Supabase/PostgREST call into a proper Error\n * with a descriptive message. PostgREST errors are plain objects with `message`,\n * `code`, `details`, and `hint` — they are NOT `instanceof Error`, which causes\n * generic catch blocks to lose the real reason.\n */\nexport function pgErrorToError(err: unknown): Error {\n if (err instanceof Error) return err;\n if (err && typeof err === \"object\") {\n const e = err as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof e.message === \"string\" && e.message) parts.push(e.message);\n if (typeof e.details === \"string\" && e.details)\n parts.push(`Details: ${e.details}`);\n if (typeof e.hint === \"string\" && e.hint) parts.push(`Hint: ${e.hint}`);\n if (typeof e.code === \"string\" && e.code) parts.push(`(code: ${e.code})`);\n if (parts.length > 0) return new Error(parts.join(\" — \"));\n }\n return new Error(String(err));\n}\n\nexport * from \"./credentials\";\nexport * from \"./net\";\n","\"use client\";\n\n/**\n * `useRealtimeChannel` — a Supabase Realtime subscription that stays alive.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 18 React modules open a\n * `postgres_changes` channel by hand. Only ONE of them reconnects. The rest\n * share a defect class that is invisible in testing and brutal in production:\n * a channel that drops (laptop sleep, a proxy idling the socket, a Realtime\n * deploy) never comes back, so the screen quietly stops updating and nothing\n * anywhere says so. The user believes stale data.\n *\n * Every hard part lives here:\n *\n * - **Unique topic per mount** (`uniqueChannelTopic`) — React 19\n * double-invokes effects and Fast Refresh re-runs them; a shared topic\n * collides with a still-joined channel and the second subscribe never\n * fires.\n * - **Reconnect with exponential backoff and a stability reset** — a channel\n * that stayed up for a while starts its next backoff from the bottom\n * instead of inheriting an hour-old delay.\n * - **Catch-up on reconnect** (`onReconnect`) — the events that happened\n * while the socket was down are GONE. Reconnecting without a re-read\n * leaves a permanently wrong screen; this hook makes the re-read part of\n * reconnecting rather than something each caller remembers.\n * - **Honest status** — `connecting | connected | reconnecting | disconnected`,\n * so a surface can tell the user it is stale instead of lying silently.\n * - **Latest-callback refs** — a new inline handler on every render must not\n * tear down and rebuild the subscription (the \"why does my realtime\n * reconnect forever\" bug).\n * - **Guaranteed teardown**, including for a channel that is still mid-join.\n *\n * The client type is STRUCTURAL: this package never imports\n * `@supabase/supabase-js`. A real Supabase client satisfies it by shape (there\n * is a compile-time proof of that in `use-realtime-channel.test.tsx`).\n */\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { uniqueChannelTopic } from \"../db/realtime\";\n\nexport type RealtimeStatus =\n | \"connecting\"\n | \"connected\"\n | \"reconnecting\"\n | \"disconnected\";\n\nexport type PostgresChangeEvent = \"*\" | \"INSERT\" | \"UPDATE\" | \"DELETE\";\n\n/** The payload shape `postgres_changes` delivers. */\nexport interface PostgresChangePayload {\n eventType?: string;\n new?: unknown;\n old?: unknown;\n}\n\nexport interface PostgresChangeBinding {\n event: PostgresChangeEvent;\n schema: string;\n table: string;\n /** PostgREST filter, e.g. `file_id=eq.<uuid>`. */\n filter?: string;\n onChange: (payload: PostgresChangePayload) => void;\n}\n\n/** The `postgres_changes` filter, exactly as Supabase declares it. */\nexport interface PostgresChangeFilter {\n event: PostgresChangeEvent;\n schema: string;\n table?: string;\n filter?: string;\n}\n\n/**\n * Structural stand-in for `RealtimeChannel`. The signature must match\n * Supabase's `postgres_changes` overload PRECISELY — a wider `filter` type or\n * a `never`-typed callback makes the real client fail to satisfy this\n * interface, and the host discovers it at its own typecheck. That is exactly\n * what the compile-time proof in `use-realtime-channel.test.tsx` caught during\n * this package's own build.\n */\nexport interface RealtimeChannelLike {\n on(\n type: \"postgres_changes\",\n filter: PostgresChangeFilter,\n callback: (payload: PostgresChangePayload) => void,\n ): RealtimeChannelLike;\n subscribe(callback?: (status: string, error?: Error) => void): unknown;\n}\n\n/** Structural stand-in for `SupabaseClient` (realtime surface only). */\nexport interface RealtimeClientLike {\n channel(topic: string): RealtimeChannelLike;\n removeChannel(channel: RealtimeChannelLike): unknown;\n}\n\nexport interface UseRealtimeChannelOptions {\n /** Turn the subscription off without unmounting. Default `true`. */\n enabled?: boolean;\n /**\n * Called after a DROPPED channel comes back. This is the catch-up door:\n * re-read whatever the missed events would have changed.\n */\n onReconnect?: () => void;\n /** First reconnect delay in ms; doubles up to `maxReconnectDelayMs`. Default `1000`. */\n reconnectDelayMs?: number;\n /** Ceiling for the reconnect backoff. Default `30000`. */\n maxReconnectDelayMs?: number;\n /**\n * How long a channel must stay connected before its backoff resets to the\n * floor. Default `30000` — without it a flapping connection inherits an\n * ever-growing delay and effectively stops trying.\n */\n backoffResetAfterMs?: number;\n}\n\nexport interface RealtimeChannelState {\n status: RealtimeStatus;\n /** True once the channel has dropped at least once this mount. */\n hasReconnected: boolean;\n}\n\nexport function useRealtimeChannel(\n client: RealtimeClientLike | null | undefined,\n topic: string,\n bindings: readonly PostgresChangeBinding[],\n options: UseRealtimeChannelOptions = {},\n): RealtimeChannelState {\n const {\n enabled = true,\n onReconnect,\n reconnectDelayMs = 1_000,\n maxReconnectDelayMs = 30_000,\n backoffResetAfterMs = 30_000,\n } = options;\n\n const [status, setStatus] = useState<RealtimeStatus>(\n enabled && client ? \"connecting\" : \"disconnected\",\n );\n const [hasReconnected, setHasReconnected] = useState(false);\n\n const bindingsRef = useRef(bindings);\n bindingsRef.current = bindings;\n const onReconnectRef = useRef(onReconnect);\n onReconnectRef.current = onReconnect;\n\n // The binding SHAPE (not the handler identities) is what must re-subscribe.\n const shape = bindings\n .map((b) => `${b.event}:${b.schema}.${b.table}:${b.filter ?? \"\"}`)\n .join(\"|\");\n\n useEffect(() => {\n if (!client || !enabled) {\n setStatus(\"disconnected\");\n return undefined;\n }\n\n let disposed = false;\n let attempt = 0;\n let everConnected = false;\n let connectedAt = 0;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let channel: RealtimeChannelLike | null = null;\n\n const teardown = (): void => {\n if (!channel) return;\n const dead = channel;\n channel = null;\n try {\n void client.removeChannel(dead);\n } catch {\n // A channel torn down mid-join can reject; the socket drops it anyway\n // and there is nothing a caller could do about it.\n }\n };\n\n const scheduleReconnect = (): void => {\n if (disposed) return;\n // A channel that held for a while earned a fresh backoff floor.\n if (everConnected && Date.now() - connectedAt >= backoffResetAfterMs) {\n attempt = 0;\n }\n const delay = Math.min(\n reconnectDelayMs * 2 ** attempt,\n maxReconnectDelayMs,\n );\n attempt += 1;\n setStatus(everConnected ? \"reconnecting\" : \"connecting\");\n retryTimer = setTimeout(() => {\n teardown();\n connect();\n }, delay);\n };\n\n const connect = (): void => {\n if (disposed) return;\n let next = client.channel(uniqueChannelTopic(topic));\n for (const [index, binding] of bindingsRef.current.entries()) {\n next = next.on(\n \"postgres_changes\",\n {\n event: binding.event,\n schema: binding.schema,\n table: binding.table,\n ...(binding.filter !== undefined ? { filter: binding.filter } : {}),\n },\n (payload: PostgresChangePayload) => {\n // Read through the ref so a re-rendered inline handler is used\n // without rebuilding the subscription.\n bindingsRef.current[index]?.onChange(payload);\n },\n );\n }\n channel = next;\n next.subscribe((subscribeStatus) => {\n if (disposed) return;\n if (subscribeStatus === \"SUBSCRIBED\") {\n const rejoined = everConnected;\n everConnected = true;\n connectedAt = Date.now();\n attempt = 0;\n setStatus(\"connected\");\n if (rejoined) {\n setHasReconnected(true);\n onReconnectRef.current?.();\n }\n return;\n }\n if (\n subscribeStatus === \"CHANNEL_ERROR\" ||\n subscribeStatus === \"TIMED_OUT\" ||\n subscribeStatus === \"CLOSED\"\n ) {\n scheduleReconnect();\n }\n });\n };\n\n connect();\n\n return () => {\n disposed = true;\n if (retryTimer !== undefined) clearTimeout(retryTimer);\n teardown();\n };\n }, [\n client,\n enabled,\n topic,\n shape,\n reconnectDelayMs,\n maxReconnectDelayMs,\n backoffResetAfterMs,\n ]);\n\n return { status, hasReconnected };\n}\n","/**\n * Realtime channel hygiene — RE-EXPORT ONLY. The canonical home is\n * `@ai-matrx/realtime`.\n *\n * Channel NAMING belongs to the realtime package (its work order, R1): it owns\n * the channel-naming registry, and `uniqueChannelTopic` is the React-19\n * double-invoked-effect collision guard that registry hands out. This package\n * kept the only copy while `@ai-matrx/realtime` was unpublished; that copy is\n * now DELETED and this module forwards, so the two can never drift and the\n * `#`-suffixed instance topics a Matrx app opens all come from one counter.\n *\n * Why forwarding matters and a second copy would not do: the old\n * implementation kept its sequence in a module-level `let`. With unsplit dual\n * ESM/CJS output a module can be instantiated twice in one process, so two\n * counters both starting at 1 hand out the same \"unique\" topic twice — exactly\n * the collision the function exists to prevent. The realtime package keeps the\n * counter in a `globalThis` slot (`Symbol.for`) and proves it across module\n * graphs in its packed-tarball canary.\n *\n * `@ai-matrx/realtime` is the deeper layer (zero runtime dependencies; it does\n * not and must not depend on `@ai-matrx/data`), so this direction keeps the\n * sibling graph acyclic — `node scripts/check_ts_sibling_graph.mjs`.\n *\n * New realtime work should not reach for this at all: declare a channel\n * namespace with `defineChannelNamespace` and let the manager assign the\n * instance topic. This export exists for the call sites that have not yet\n * migrated off hand-rolled `.channel(` blocks.\n */\n\nexport { uniqueChannelTopic } from \"@ai-matrx/realtime\";\n","\"use client\";\n\n/**\n * `useRealtimeQuery` — a read that stays current, in one call.\n *\n * This is the exact shape 8+ matrx-frontend hooks hand-assemble: load a list,\n * subscribe to the rows behind it, refresh on change. Composing the two\n * primitives here (rather than in each host hook) closes the gap none of the\n * hand-rolled copies close — a change that arrives while the socket is down is\n * never delivered, so reconnecting MUST re-read. `useRealtimeChannel`'s\n * `onReconnect` is wired to `refresh` for you.\n *\n * Realtime events are COALESCED: a bulk write firing 200 INSERTs must not\n * produce 200 reads. Changes inside `debounceMs` collapse into one refresh.\n */\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport {\n useAsyncData,\n type AsyncData,\n type AsyncDataContext,\n type UseAsyncDataOptions,\n} from \"./use-async-data\";\nimport {\n useRealtimeChannel,\n type PostgresChangeBinding,\n type PostgresChangeEvent,\n type RealtimeClientLike,\n type RealtimeStatus,\n type UseRealtimeChannelOptions,\n} from \"./use-realtime-channel\";\n\n/** A table to watch. `onChange` is optional — refreshing is the default. */\nexport interface WatchedTable {\n event?: PostgresChangeEvent;\n schema: string;\n table: string;\n filter?: string;\n}\n\nexport interface UseRealtimeQueryOptions<T>\n extends UseAsyncDataOptions<T>,\n Pick<\n UseRealtimeChannelOptions,\n \"reconnectDelayMs\" | \"maxReconnectDelayMs\" | \"backoffResetAfterMs\"\n > {\n /** Coalesce bursts of realtime events into one refresh. Default `120`ms. */\n debounceMs?: number;\n}\n\nexport interface RealtimeQuery<T> extends AsyncData<T> {\n realtimeStatus: RealtimeStatus;\n}\n\nexport function useRealtimeQuery<T>(\n args: {\n client: RealtimeClientLike | null | undefined;\n topic: string;\n watch: readonly WatchedTable[];\n load: (context: AsyncDataContext) => Promise<T>;\n deps: readonly unknown[];\n },\n options: UseRealtimeQueryOptions<T> = {},\n): RealtimeQuery<T> {\n const {\n debounceMs = 120,\n reconnectDelayMs,\n maxReconnectDelayMs,\n backoffResetAfterMs,\n ...dataOptions\n } = options;\n const query = useAsyncData(args.load, args.deps, dataOptions);\n\n const refreshRef = useRef(query.refresh);\n refreshRef.current = query.refresh;\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(\n () => () => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n },\n [],\n );\n\n const shape = args.watch\n .map((w) => `${w.event ?? \"*\"}:${w.schema}.${w.table}:${w.filter ?? \"\"}`)\n .join(\"|\");\n\n const bindings = useMemo<PostgresChangeBinding[]>(() => {\n const coalescedRefresh = (): void => {\n if (timerRef.current !== undefined) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => {\n timerRef.current = undefined;\n refreshRef.current();\n }, debounceMs);\n };\n return args.watch.map((w) => ({\n event: w.event ?? \"*\",\n schema: w.schema,\n table: w.table,\n ...(w.filter !== undefined ? { filter: w.filter } : {}),\n onChange: coalescedRefresh,\n }));\n // `shape` is the stable identity of `args.watch`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shape, debounceMs]);\n\n const { status } = useRealtimeChannel(args.client, args.topic, bindings, {\n enabled: dataOptions.enabled ?? true,\n ...(reconnectDelayMs !== undefined ? { reconnectDelayMs } : {}),\n ...(maxReconnectDelayMs !== undefined ? { maxReconnectDelayMs } : {}),\n ...(backoffResetAfterMs !== undefined ? { backoffResetAfterMs } : {}),\n // The events lost while the socket was down are unrecoverable; the read is\n // the only way back to truth.\n onReconnect: () => refreshRef.current(),\n });\n\n return { ...query, realtimeStatus: status };\n}\n","\"use client\";\n\n/**\n * `useAsyncAction` — a write with honest pending/error state.\n *\n * WHY (measured in matrx-frontend, 2026-08-30): 554 components hold their own\n * `saving`/`submitting`/`deleting` boolean around an async call. The repeated\n * defects:\n *\n * 1. **No double-submit guard.** A disabled button is not one — a fast second\n * click, an Enter key, or a re-render race all get through, and the write\n * runs twice. Here a second `run` while one is in flight is REFUSED and\n * says so (`{ ok: false, error: ActionInFlightError }`) instead of quietly\n * duplicating a row.\n * 2. **`setState` after unmount**, because `finally { setSaving(false) }`\n * does not know the component is gone.\n * 3. **`String(err)` again** — a PostgREST error object renders as\n * \"[object Object]\". Every failure here goes through `pgErrorToError`.\n * 4. **The error is thrown AND stored, or neither.** This hook returns a\n * discriminated result so a caller can branch without try/catch, while\n * `error` holds the same `Error` for rendering. Nothing is swallowed:\n * the failure is in the return value, in `error`, and in `onError`.\n *\n * Writes are NEVER retried here — that is the whole point of the boundary. A\n * user-authored submission replayed after an ambiguous timeout is how you get\n * two invoices. Retry belongs on idempotent reads (`useAsyncData`).\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\n\n/** Thrown-shaped refusal when a second run overlaps an in-flight one. */\nexport class ActionInFlightError extends Error {\n constructor() {\n super(\n \"This action is already running. The duplicate submission was refused — \" +\n \"wait for the in-flight action to finish.\",\n );\n this.name = \"ActionInFlightError\";\n }\n}\n\nexport type ActionResult<R> =\n | { ok: true; value: R }\n | { ok: false; error: Error };\n\nexport interface UseAsyncActionOptions<R> {\n onSuccess?: (value: R) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface AsyncAction<Args extends readonly unknown[], R> {\n run: (...args: Args) => Promise<ActionResult<R>>;\n pending: boolean;\n /** The last failure, as a real `Error`. Cleared when a new run starts. */\n error: Error | null;\n /** Clear `error` without running anything. */\n reset: () => void;\n}\n\nexport function useAsyncAction<Args extends readonly unknown[], R>(\n action: (...args: Args) => Promise<R>,\n options: UseAsyncActionOptions<R> = {},\n): AsyncAction<Args, R> {\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const actionRef = useRef(action);\n actionRef.current = action;\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const inFlightRef = useRef(false);\n const mountedRef = useRef(true);\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const run = useCallback(async (...args: Args): Promise<ActionResult<R>> => {\n if (inFlightRef.current) {\n const refusal = new ActionInFlightError();\n if (mountedRef.current) setError(refusal);\n optionsRef.current.onError?.(refusal);\n return { ok: false, error: refusal };\n }\n inFlightRef.current = true;\n if (mountedRef.current) {\n setPending(true);\n setError(null);\n }\n try {\n const value = await actionRef.current(...args);\n if (mountedRef.current) setPending(false);\n optionsRef.current.onSuccess?.(value);\n return { ok: true, value };\n } catch (raw) {\n const failure = pgErrorToError(raw);\n if (mountedRef.current) {\n setPending(false);\n setError(failure);\n }\n optionsRef.current.onError?.(failure);\n return { ok: false, error: failure };\n } finally {\n inFlightRef.current = false;\n }\n }, []);\n\n const reset = useCallback(() => setError(null), []);\n\n return { run, pending, error, reset };\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { pgErrorToError } from \"../index\";\n\nexport interface PaginatedPage<T, C extends string | number> {\n rows: T[];\n nextCursor: C | null;\n totalItems?: number;\n}\n\nexport interface PaginatedDataOptions<T, C extends string | number> {\n /** Immutable identity of every input that changes the source result. */\n queryKey: string;\n initialCursor: C;\n loadPage: (context: { cursor: C; signal: AbortSignal }) => Promise<PaginatedPage<T, C>>;\n getRowId: (row: T) => string;\n enabled?: boolean;\n onError?: (error: Error) => void;\n /**\n * Optional, opt-in interval for rebuilding the visible source window in the\n * background. Hidden documents and an active pagination request are skipped.\n */\n refreshIntervalMs?: number;\n /** Runs once immediately before an interval-driven source reload begins. */\n onBeforeBackgroundRefresh?: () => void | Promise<void>;\n}\n\nexport interface PaginatedData<T> {\n queryKey: string;\n rows: T[];\n loading: boolean;\n /** True while a periodic refresh rebuilds the current window behind its rows. */\n isRefreshing?: boolean;\n isFetchingNextPage: boolean;\n error: Error | null;\n hasNextPage: boolean;\n loadNextPage: () => Promise<void>;\n refresh: () => void;\n totalItems: number | undefined;\n isLoadingAll: boolean;\n requestLoadAll: () => void;\n stopLoadingAll: () => void;\n}\n\ntype Phase = \"idle\" | \"initial\" | \"ready\" | \"next\" | \"refreshing\" | \"refresh-error\" | \"initial-error\" | \"next-error\" | \"cursor-error\";\n\ninterface Snapshot<T, C extends string | number> {\n queryKey: string | null;\n initialCursor: C | null;\n rows: T[];\n nextCursor: C | null;\n totalItems: number | undefined;\n error: Error | null;\n phase: Phase;\n}\n\nfunction isAbort(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === \"AbortError\";\n}\n\nfunction mergeRows<T>(current: readonly T[], incoming: readonly T[], getRowId: (row: T) => string): T[] {\n const next = [...current];\n const indexById = new Map<string, number>();\n current.forEach((row, index) => indexById.set(getRowId(row), index));\n for (const row of incoming) {\n const id = getRowId(row);\n const existing = indexById.get(id);\n if (existing === undefined) {\n indexById.set(id, next.length);\n next.push(row);\n } else {\n // The server's newer representation wins without moving visible rows.\n next[existing] = row;\n }\n }\n return next;\n}\n\n/** Cursor accumulation with no cache and no table dependency. */\nexport function usePaginatedData<T, C extends string | number>(\n options: PaginatedDataOptions<T, C>,\n): PaginatedData<T> {\n const { queryKey, initialCursor, enabled = true } = options;\n const [refreshTick, setRefreshTick] = useState(0);\n const [loadAllRequested, setLoadAllRequested] = useState(false);\n const [snapshot, setSnapshot] = useState<Snapshot<T, C>>({\n queryKey: null, initialCursor: null, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"idle\",\n });\n const snapshotRef = useRef(snapshot);\n const queryKeyRef = useRef(queryKey);\n queryKeyRef.current = queryKey;\n const initialCursorRef = useRef(initialCursor);\n initialCursorRef.current = initialCursor;\n const enabledRef = useRef(enabled);\n enabledRef.current = enabled;\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const generationRef = useRef(0);\n const inFlightRef = useRef(false);\n const controllerRef = useRef<AbortController | null>(null);\n const receiptsRef = useRef(new Set<C>());\n\n const publish = useCallback((next: Snapshot<T, C>) => {\n snapshotRef.current = next;\n setSnapshot(next);\n }, []);\n const isCurrent = useCallback((requestKey: string, requestInitialCursor: C, generation: number, signal: AbortSignal) => (\n !signal.aborted && generation === generationRef.current && requestKey === queryKeyRef.current && requestInitialCursor === initialCursorRef.current && enabledRef.current\n ), []);\n\n const requestPage = useCallback(async (\n cursor: C, kind: \"initial\" | \"next\", requestKey: string, requestInitialCursor: C, generation: number,\n ): Promise<void> => {\n if (inFlightRef.current || !enabledRef.current || requestKey !== queryKeyRef.current) return;\n const controller = new AbortController();\n controllerRef.current = controller;\n inFlightRef.current = true;\n publish({ ...snapshotRef.current, queryKey: requestKey, initialCursor: requestInitialCursor, error: null, phase: kind });\n try {\n const page = await optionsRef.current.loadPage({ cursor, signal: controller.signal });\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n const rows = mergeRows(\n kind === \"initial\" ? [] : snapshotRef.current.rows,\n page.rows,\n optionsRef.current.getRowId,\n );\n receiptsRef.current.add(cursor);\n const repeatedCursor = page.nextCursor !== null && receiptsRef.current.has(page.nextCursor);\n const error = repeatedCursor\n ? new Error(`Pagination source returned cursor \"${String(page.nextCursor)}\" more than once; loading stopped to prevent a duplicate-page loop.`)\n : null;\n // Publish after clearing the one-flight latch: load-all's effect observes\n // this terminal page state and may immediately schedule exactly one more.\n inFlightRef.current = false;\n controllerRef.current = null;\n publish({\n queryKey: requestKey,\n initialCursor: requestInitialCursor,\n rows,\n nextCursor: error ? null : page.nextCursor,\n totalItems: page.totalItems ?? snapshotRef.current.totalItems,\n error,\n phase: error ? \"cursor-error\" : \"ready\",\n });\n if (error) optionsRef.current.onError?.(error);\n if (error) setLoadAllRequested(false);\n } catch (raw) {\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal) || isAbort(raw)) return;\n const error = pgErrorToError(raw);\n publish({ ...snapshotRef.current, queryKey: requestKey, initialCursor: requestInitialCursor, error, phase: kind === \"initial\" ? \"initial-error\" : \"next-error\" });\n setLoadAllRequested(false);\n optionsRef.current.onError?.(error);\n } finally {\n if (isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) {\n inFlightRef.current = false;\n controllerRef.current = null;\n }\n }\n }, [isCurrent, publish]);\n\n const refreshLoadedWindow = useCallback(async (): Promise<void> => {\n const current = snapshotRef.current;\n const requestKey = queryKeyRef.current;\n const requestInitialCursor = initialCursorRef.current;\n if (\n inFlightRef.current || !enabledRef.current || current.queryKey !== requestKey\n || current.initialCursor !== requestInitialCursor || current.phase === \"initial\"\n ) return;\n\n const generation = generationRef.current;\n const controller = new AbortController();\n controllerRef.current = controller;\n inFlightRef.current = true;\n // Keep the last complete receipt on screen until the replacement window is complete.\n publish({ ...current, error: null, phase: \"refreshing\" });\n try {\n await optionsRef.current.onBeforeBackgroundRefresh?.();\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n\n const targetRowCount = current.rows.length;\n const receipts = new Set<C>();\n let cursor = requestInitialCursor;\n let rows: T[] = [];\n let nextCursor: C | null = null;\n let totalItems = current.totalItems;\n while (true) {\n const page = await optionsRef.current.loadPage({ cursor, signal: controller.signal });\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n rows = mergeRows(rows, page.rows, optionsRef.current.getRowId);\n receipts.add(cursor);\n totalItems = page.totalItems ?? totalItems;\n nextCursor = page.nextCursor;\n if (nextCursor !== null && receipts.has(nextCursor)) {\n throw new Error(`Pagination source returned cursor \"${String(nextCursor)}\" more than once; background refresh stopped to prevent a duplicate-page loop.`);\n }\n // Rebuild at least the existing visible window, unless the new source ends first.\n if (rows.length >= targetRowCount || nextCursor === null) break;\n cursor = nextCursor;\n }\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) return;\n receiptsRef.current = receipts;\n publish({\n queryKey: requestKey,\n initialCursor: requestInitialCursor,\n rows,\n nextCursor,\n totalItems,\n error: null,\n phase: \"ready\",\n });\n } catch (raw) {\n if (!isCurrent(requestKey, requestInitialCursor, generation, controller.signal) || isAbort(raw)) return;\n const error = pgErrorToError(raw);\n // The callback may have advanced an unkeyed source cutoff. Retain the\n // visible receipt, but never append its old cursor chain to that source.\n publish({ ...current, error, phase: \"refresh-error\" });\n setLoadAllRequested(false);\n optionsRef.current.onError?.(error);\n } finally {\n if (isCurrent(requestKey, requestInitialCursor, generation, controller.signal)) {\n inFlightRef.current = false;\n controllerRef.current = null;\n }\n }\n }, [isCurrent, publish]);\n\n useEffect(() => {\n generationRef.current += 1;\n const generation = generationRef.current;\n controllerRef.current?.abort();\n controllerRef.current = null;\n inFlightRef.current = false;\n receiptsRef.current = new Set<C>();\n setLoadAllRequested(false);\n if (!enabled) {\n publish({ queryKey, initialCursor, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"idle\" });\n return undefined;\n }\n // A former auth/org/filter identity must not remain visible for one paint.\n publish({ queryKey, initialCursor, rows: [], nextCursor: null, totalItems: undefined, error: null, phase: \"initial\" });\n void requestPage(initialCursor, \"initial\", queryKey, initialCursor, generation);\n return () => controllerRef.current?.abort();\n }, [enabled, initialCursor, publish, queryKey, refreshTick, requestPage]);\n\n const loadNextPage = useCallback(async (): Promise<void> => {\n const current = snapshotRef.current;\n const requestKey = queryKeyRef.current;\n if (!enabledRef.current || inFlightRef.current || current.queryKey !== requestKey || current.initialCursor !== initialCursorRef.current) return;\n const generation = generationRef.current;\n if (current.phase === \"initial-error\") {\n receiptsRef.current = new Set<C>();\n await requestPage(optionsRef.current.initialCursor, \"initial\", requestKey, optionsRef.current.initialCursor, generation);\n return;\n }\n if (current.phase === \"cursor-error\" || current.phase === \"refresh-error\" || current.nextCursor === null) return;\n if (receiptsRef.current.has(current.nextCursor)) {\n const error = new Error(`Pagination source tried to load cursor \"${String(current.nextCursor)}\" twice; loading stopped to prevent a duplicate-page loop.`);\n publish({ ...current, error, nextCursor: null, phase: \"cursor-error\" });\n optionsRef.current.onError?.(error);\n return;\n }\n await requestPage(current.nextCursor, \"next\", requestKey, current.initialCursor ?? optionsRef.current.initialCursor, generation);\n }, [publish, requestPage]);\n\n const refresh = useCallback(() => setRefreshTick((tick) => tick + 1), []);\n const requestLoadAll = useCallback(() => setLoadAllRequested(true), []);\n const stopLoadingAll = useCallback(() => setLoadAllRequested(false), []);\n\n useEffect(() => {\n const interval = options.refreshIntervalMs;\n if (!enabled || interval === undefined || !Number.isFinite(interval) || interval <= 0) return undefined;\n const timer = window.setInterval(() => {\n if (typeof document !== \"undefined\" && document.hidden) return;\n void refreshLoadedWindow();\n }, interval);\n return () => window.clearInterval(timer);\n }, [enabled, options.refreshIntervalMs, refreshLoadedWindow]);\n\n useEffect(() => {\n if (!loadAllRequested || inFlightRef.current) return;\n const current = snapshotRef.current;\n if (current.phase === \"ready\" && current.nextCursor !== null) void loadNextPage();\n else if (current.phase === \"cursor-error\" || current.phase === \"refresh-error\" || current.nextCursor === null) setLoadAllRequested(false);\n }, [loadAllRequested, loadNextPage, snapshot]);\n const visible = enabled && snapshot.queryKey === queryKey && snapshot.initialCursor === initialCursor;\n const phase = visible ? snapshot.phase : \"initial\";\n return {\n queryKey,\n rows: visible ? snapshot.rows : [],\n loading: enabled && phase === \"initial\",\n isRefreshing: visible && phase === \"refreshing\",\n isFetchingNextPage: visible && phase === \"next\",\n error: visible ? snapshot.error : null,\n hasNextPage: visible && phase !== \"cursor-error\" && phase !== \"refresh-error\" && (snapshot.nextCursor !== null || phase === \"initial-error\"),\n loadNextPage,\n refresh,\n totalItems: visible ? snapshot.totalItems : undefined,\n isLoadingAll: visible && loadAllRequested,\n requestLoadAll,\n stopLoadingAll,\n };\n}\n","/** Framework-free validation shared by every host that resolves tables.pagination. */\nexport type ScrollPaginationPolicy =\n | { mode: \"scroll\"; thresholdPx: number; intentTimeoutMs: number; valid: true }\n /** `approvedBy` is recorded attribution from the configured policy, not an authentication assertion. */\n | { mode: \"manual\"; reason: string; approvedBy: string; valid: true }\n | { mode: \"suspended\"; reason: string; valid: false };\n\nexport function resolveScrollPaginationPolicy(values: {\n mode: unknown;\n thresholdPx: unknown;\n intentTimeoutMs: unknown;\n reason?: unknown;\n approvedBy?: unknown;\n}): ScrollPaginationPolicy {\n const thresholdPx = values.thresholdPx;\n const intentTimeoutMs = values.intentTimeoutMs;\n const dimensionsValid = typeof thresholdPx === \"number\" && Number.isFinite(thresholdPx) && thresholdPx >= 0 &&\n typeof intentTimeoutMs === \"number\" && Number.isFinite(intentTimeoutMs) && intentTimeoutMs > 0;\n if (values.mode === \"scroll\") {\n return dimensionsValid\n ? { mode: \"scroll\", thresholdPx, intentTimeoutMs, valid: true }\n : suspendScrollPaginationPolicy(\"Automatic loading is paused because its scrolling preferences are missing or invalid.\");\n }\n if (values.mode === \"manual\") {\n const reason = nonBlankString(values.reason);\n const approvedBy = nonBlankString(values.approvedBy);\n return reason && approvedBy\n ? { mode: \"manual\", reason, approvedBy, valid: true }\n : suspendScrollPaginationPolicy(\"Automatic loading is paused because the manual exception is missing its reason or approving owner.\");\n }\n return suspendScrollPaginationPolicy(\"Automatic loading is paused because its pagination mode is missing or invalid.\");\n}\n\n/** A temporary configuration state keeps explicit Load more usable without inventing an approval. */\nexport function suspendScrollPaginationPolicy(reason: string): ScrollPaginationPolicy {\n return { mode: \"suspended\", reason, valid: false };\n}\n\nfunction nonBlankString(value: unknown): string | null {\n return typeof value === \"string\" && value.trim().length > 0 ? value.trim() : null;\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","useState","useRef","useEffect","useEffect","useRef","useRef","useEffect","useCallback","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","useCallback","useEffect","useRef","useState","isAbort","useState","useRef","useCallback","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/data",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
4
4
  "description": "The opinionated AI Matrx data layer: the /db tier's hard-won Supabase primitives — optimistic concurrency, complete-list reads, JSONB merge, session-retry, the domain-wide auth cookie, realtime channel hygiene — the /net resilience trio, and the /files lane: the Matrx file-communication client (durable-ref resolution, the mx_files_session bootstrap, authenticated blob fetch, uploads, the public-link door).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",