@nifrajs/web-react 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/dist/await.d.ts +29 -0
  4. package/dist/await.d.ts.map +1 -0
  5. package/dist/await.js +50 -0
  6. package/dist/await.js.map +1 -0
  7. package/dist/client.d.ts +12 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +39 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/compose.d.ts +9 -0
  12. package/dist/compose.d.ts.map +1 -0
  13. package/dist/compose.js +16 -0
  14. package/dist/compose.js.map +1 -0
  15. package/dist/content.d.ts +18 -0
  16. package/dist/content.d.ts.map +1 -0
  17. package/dist/content.js +14 -0
  18. package/dist/content.js.map +1 -0
  19. package/dist/error.d.ts +8 -0
  20. package/dist/error.d.ts.map +1 -0
  21. package/dist/error.js +32 -0
  22. package/dist/error.js.map +1 -0
  23. package/dist/fetcher.d.ts +23 -0
  24. package/dist/fetcher.d.ts.map +1 -0
  25. package/dist/fetcher.js +45 -0
  26. package/dist/fetcher.js.map +1 -0
  27. package/dist/i18n.d.ts +20 -0
  28. package/dist/i18n.d.ts.map +1 -0
  29. package/dist/i18n.js +25 -0
  30. package/dist/i18n.js.map +1 -0
  31. package/dist/image.d.ts +25 -0
  32. package/dist/image.d.ts.map +1 -0
  33. package/dist/image.js +21 -0
  34. package/dist/image.js.map +1 -0
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +29 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/island.d.ts +27 -0
  40. package/dist/island.d.ts.map +1 -0
  41. package/dist/island.js +23 -0
  42. package/dist/island.js.map +1 -0
  43. package/dist/query.d.ts +17 -0
  44. package/dist/query.d.ts.map +1 -0
  45. package/dist/query.js +58 -0
  46. package/dist/query.js.map +1 -0
  47. package/package.json +101 -0
  48. package/src/await.ts +75 -0
  49. package/src/client.ts +42 -0
  50. package/src/compose.ts +17 -0
  51. package/src/content.ts +27 -0
  52. package/src/error.ts +33 -0
  53. package/src/fetcher.ts +66 -0
  54. package/src/i18n.ts +36 -0
  55. package/src/image.ts +43 -0
  56. package/src/index.ts +30 -0
  57. package/src/island.ts +39 -0
  58. package/src/query.ts +75 -0
package/src/query.ts ADDED
@@ -0,0 +1,75 @@
1
+ import {
2
+ createQueryClient,
3
+ type QueryClient,
4
+ type QueryHandle,
5
+ type QueryState,
6
+ } from "@nifrajs/web"
7
+ /**
8
+ * `@nifrajs/web-react/query` — React bindings for the keyed query-cache. `useQuery(key, fn)` subscribes a
9
+ * component to a query (via `useSyncExternalStore`) and fetches on mount / key change; `useQueryClient`
10
+ * exposes `invalidateQueries` for after a mutation. Imports only `react` + `@nifrajs/web` (never
11
+ * `react-dom/*`), so route components use it on the server *and* client. No JSX (the package builds
12
+ * with plain `tsc`).
13
+ *
14
+ * The query client is created lazily **client-side only** (the `typeof window` guard) — on the server
15
+ * there is none, so `useQuery` renders the idle/pending state (queries are client-first; loaders are
16
+ * the SSR data source). The first client render uses the same idle server-snapshot, so no mismatch.
17
+ */
18
+ import { useEffect, useSyncExternalStore } from "react"
19
+
20
+ let client: QueryClient | undefined
21
+ function getClient(): QueryClient | undefined {
22
+ if (typeof window === "undefined") return undefined // SSR → no client (queries render idle)
23
+ if (client === undefined) client = createQueryClient({ now: () => Date.now() })
24
+ return client
25
+ }
26
+
27
+ /** Access the query client to imperatively `invalidateQueries(keyOrPrefix)` (e.g. after a mutation). */
28
+ export function useQueryClient(): Pick<QueryClient, "invalidateQueries"> {
29
+ return getClient() ?? { invalidateQueries: () => {} }
30
+ }
31
+
32
+ // Stable idle snapshot for the server / pre-fetch render (stable ref → no loop, no hydration mismatch).
33
+ const IDLE: QueryState<never> = Object.freeze({
34
+ status: "pending",
35
+ data: undefined,
36
+ error: undefined,
37
+ isFetching: false,
38
+ updatedAt: Number.NEGATIVE_INFINITY,
39
+ })
40
+ const idleSnapshot = (): QueryState<never> => IDLE
41
+ const noopSubscribe = (): (() => void) => () => {}
42
+ const noopAsync = async (): Promise<never> => {
43
+ throw new Error("[nifra/web-react] useQuery.refetch called with no query client (server?)")
44
+ }
45
+
46
+ /** A query's reactive {@link QueryState} plus `isPending` + `refetch`. */
47
+ export interface UseQueryResult<T> extends QueryState<T> {
48
+ /** `status === "pending"` — no data yet (initial load). */
49
+ readonly isPending: boolean
50
+ /** Force a refetch (ignores `staleTime`). */
51
+ readonly refetch: () => Promise<T>
52
+ }
53
+
54
+ /**
55
+ * Subscribe to the keyed query for `key`, fetched via `fn`. Returns `{ status, data, error, isFetching,
56
+ * updatedAt, isPending, refetch }`. Concurrent `useQuery`s with the same key share one cache entry +
57
+ * one in-flight fetch (dedup). Refetches on mount and when the key changes; SSR-idle.
58
+ */
59
+ export function useQuery<T>(key: unknown, fn: () => Promise<T>): UseQueryResult<T> {
60
+ const handle: QueryHandle<T> | undefined = getClient()?.query<T>(key, fn)
61
+ const state = useSyncExternalStore<QueryState<T>>(
62
+ handle?.subscribe ?? noopSubscribe,
63
+ handle ? handle.snapshot : idleSnapshot,
64
+ idleSnapshot,
65
+ )
66
+ // Fetch on mount + whenever the key changes (the handle is stable per key, so it changes with the key).
67
+ useEffect(() => {
68
+ if (handle !== undefined) handle.fetch().catch(() => {})
69
+ }, [handle])
70
+ return {
71
+ ...state,
72
+ isPending: state.status === "pending",
73
+ refetch: handle ? handle.refetch : (noopAsync as () => Promise<T>),
74
+ }
75
+ }