@anvil-js/client 0.0.1 → 0.0.3

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.
@@ -1,6 +1,40 @@
1
- export { AnvilClientProvider, trpc, useAnvilClient } from '../chunk-DV6XOONA.js';
2
- import { useEffect } from 'react';
1
+ import { createTRPCReact } from '@trpc/react-query';
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
+ import { useState, useEffect } from 'react';
4
+ import { httpLink } from '@trpc/client';
5
+ import { jsx } from 'react/jsx-runtime';
3
6
 
7
+ // src/react/provider.tsx
8
+ var trpc = createTRPCReact();
9
+ function AnvilClientProvider({ config, children, devFetch }) {
10
+ const [queryClient] = useState(
11
+ () => new QueryClient({
12
+ defaultOptions: {
13
+ queries: {
14
+ staleTime: 5 * 60 * 1e3,
15
+ retry: 1,
16
+ refetchOnWindowFocus: false
17
+ }
18
+ }
19
+ })
20
+ );
21
+ const [trpcClient] = useState(
22
+ () => trpc.createClient({
23
+ links: [
24
+ httpLink({
25
+ url: config.baseUrl,
26
+ headers: async () => {
27
+ const token = await config.getToken();
28
+ return token ? { authorization: `Bearer ${token}` } : {};
29
+ },
30
+ fetch: devFetch
31
+ })
32
+ ]
33
+ })
34
+ );
35
+ return /* @__PURE__ */ jsx(trpc.Provider, { client: trpcClient, queryClient, children: /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children }) });
36
+ }
37
+ var useAnvilClient = trpc.useContext;
4
38
  var useCurrentSessionQuery = () => {
5
39
  useEffect(() => {
6
40
  }, []);
@@ -10,7 +44,30 @@ var useCurrentSessionQuery = () => {
10
44
  error: null
11
45
  };
12
46
  };
47
+ function useWsEvent(client, event, handler) {
48
+ useEffect(() => {
49
+ const off = client.on(event, handler);
50
+ return off;
51
+ }, [client, event]);
52
+ }
53
+ function useWsState(client) {
54
+ const [state, setState] = useState(
55
+ client.isConnected() ? client.isReady() ? "ready" : "connected" : "disconnected"
56
+ );
57
+ useEffect(() => {
58
+ const update = () => setState(client.isConnected() ? client.isReady() ? "ready" : "connected" : "disconnected");
59
+ const off1 = client.on("open", update);
60
+ const off2 = client.on("bootstrap", update);
61
+ const off3 = client.on("close", update);
62
+ return () => {
63
+ off1();
64
+ off2();
65
+ off3();
66
+ };
67
+ }, [client]);
68
+ return state;
69
+ }
13
70
 
14
- export { useCurrentSessionQuery as useCurrentSession };
71
+ export { AnvilClientProvider, trpc, useAnvilClient, useCurrentSessionQuery as useCurrentSession, useWsEvent, useWsState };
15
72
  //# sourceMappingURL=index.js.map
16
73
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/hooks/useCurrentSession.ts"],"names":[],"mappings":";;;AASO,IAAM,yBAAyB,MAAM;AAE1C,EAAA,SAAA,CAAU,MAAM;AAAA,EAEhB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,SAAA,EAAW,KAAA;AAAA,IACX,KAAA,EAAO;AAAA,GACT;AACF","file":"index.js","sourcesContent":["import { trpc } from '../provider';\nimport { useEffect } from 'react';\n\nexport interface CurrentSession {\n valid: boolean;\n user?: { uuid: string; username: string; displayName: string; avatarUrl: string | null };\n expiresAt?: string;\n}\n\nexport const useCurrentSessionQuery = () => {\n // Real procedure doesn't exist yet (Phase 5). Return a stable shape for now.\n useEffect(() => {\n /* no-op */\n }, []);\n\n return {\n data: undefined as CurrentSession | undefined,\n isLoading: false,\n error: null,\n };\n};\n\n// Re-export the trpc client for use in custom hooks\nexport { trpc };\n"]}
1
+ {"version":3,"sources":["../../src/react/provider.tsx","../../src/react/hooks/useCurrentSession.ts","../../src/react/ws-hooks.ts"],"names":["useEffect","useState"],"mappings":";;;;;;;AASO,IAAM,OAAO,eAAA;AAyEb,SAAS,mBAAA,CAAoB,EAAE,MAAA,EAAQ,QAAA,EAAU,UAAS,EAA6B;AAC5F,EAAA,MAAM,CAAC,WAAW,CAAA,GAAI,QAAA;AAAA,IACpB,MACE,IAAI,WAAA,CAAY;AAAA,MACd,cAAA,EAAgB;AAAA,QACd,OAAA,EAAS;AAAA,UACP,SAAA,EAAW,IAAI,EAAA,GAAK,GAAA;AAAA,UACpB,KAAA,EAAO,CAAA;AAAA,UACP,oBAAA,EAAsB;AAAA;AACxB;AACF,KACD;AAAA,GACL;AAEA,EAAA,MAAM,CAAC,UAAU,CAAA,GAAI,QAAA;AAAA,IAAS,MAC5B,KAAK,YAAA,CAAa;AAAA,MAChB,KAAA,EAAO;AAAA,QACL,QAAA,CAAS;AAAA,UACP,KAAK,MAAA,CAAO,OAAA;AAAA,UACZ,SAAS,YAAY;AACnB,YAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,QAAA,EAAS;AACpC,YAAA,OAAO,QAAQ,EAAE,aAAA,EAAe,UAAU,KAAK,CAAA,CAAA,KAAO,EAAC;AAAA,UACzD,CAAA;AAAA,UACA,KAAA,EAAO;AAAA,SACR;AAAA;AACH,KACD;AAAA,GACH;AAEA,EAAA,uBACE,GAAA,CAAC,IAAA,CAAK,QAAA,EAAL,EAAc,MAAA,EAAQ,UAAA,EAAY,WAAA,EACjC,QAAA,kBAAA,GAAA,CAAC,mBAAA,EAAA,EAAoB,MAAA,EAAQ,WAAA,EAAc,QAAA,EAAS,CAAA,EACtD,CAAA;AAEJ;AAEO,IAAM,iBAAiB,IAAA,CAAK;AC7G5B,IAAM,yBAAyB,MAAM;AAE1C,EAAA,SAAA,CAAU,MAAM;AAAA,EAEhB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,MAAA;AAAA,IACN,SAAA,EAAW,KAAA;AAAA,IACX,KAAA,EAAO;AAAA,GACT;AACF;ACRO,SAAS,UAAA,CACd,MAAA,EACA,KAAA,EACA,OAAA,EACM;AACN,EAAAA,UAAU,MAAM;AACd,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,EAAA,CAAG,KAAA,EAAO,OAAO,CAAA;AACpC,IAAA,OAAO,GAAA;AAAA,EAET,CAAA,EAAG,CAAC,MAAA,EAAQ,KAAK,CAAC,CAAA;AACpB;AAMO,SAAS,WAAW,MAAA,EAAgE;AACzF,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIC,QAAAA;AAAA,IACxB,OAAO,WAAA,EAAY,GAAK,OAAO,OAAA,EAAQ,GAAI,UAAU,WAAA,GAAe;AAAA,GACtE;AACA,EAAAD,UAAU,MAAM;AACd,IAAA,MAAM,MAAA,GAAS,MACb,QAAA,CAAS,MAAA,CAAO,WAAA,EAAY,GAAK,MAAA,CAAO,OAAA,EAAQ,GAAI,OAAA,GAAU,WAAA,GAAe,cAAc,CAAA;AAC7F,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,EAAA,CAAG,MAAA,EAAQ,MAAM,CAAA;AACrC,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,EAAA,CAAG,WAAA,EAAa,MAAM,CAAA;AAC1C,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,EAAA,CAAG,OAAA,EAAS,MAAM,CAAA;AACtC,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,EAAK;AACL,MAAA,IAAA,EAAK;AACL,MAAA,IAAA,EAAK;AAAA,IACP,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACX,EAAA,OAAO,KAAA;AACT","file":"index.js","sourcesContent":["'use client';\n\nimport { createTRPCReact } from '@trpc/react-query';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { useState, type ReactNode } from 'react';\nimport { httpLink } from '@trpc/client';\nimport type { AppRouter } from '../router';\nimport type { AnvilClientConfig } from '../client/factory';\n\nexport const trpc = createTRPCReact<AppRouter>();\n\nexport interface AnvilClientProviderProps {\n config: AnvilClientConfig;\n children: ReactNode;\n}\n\n/**\n * The Anvil tRPC client.\n *\n * History note: earlier versions of this provider used\n * `splitLink({ true: wsLink(...), false: httpBatchLink(...) })` so\n * subscription procedures would multiplex over a single WebSocket.\n * That setup opened a WebSocket on every mount, retried\n * indefinitely against `ws://localhost:3001/v1` (no real WS in dev),\n * spammed the console with \"ws connection error\", and held onto\n * EventSource handles that the GC couldn't free -- a noticeable\n * memory leak on the launcher tab.\n *\n * The router now has zero `subscription` procedures\n * (chat.subscribeChannel / social.presence.get are queries with\n * client-side polling, per Phase 5b / 6c). When real subscriptions\n * land, they should use a *dedicated* WS gateway with explicit\n * connect-on-subscribe semantics (tRPC v11 `createWSClient({ lazy: true })`)\n * so a tab with no active subscription never opens a socket.\n *\n * This scaffold uses `httpLink` (one request per call) instead of\n * `httpBatchLink`. Reason: the MSW dev layer returns a single\n * response object per registered handler. tRPC v11's batched GET\n * format (`?batch=1&input=...`) expects an array response\n * (`[{ result: { data } }, ...]`) and the dev path doesn't emit\n * that shape. Single-call links avoid the entire mismatch and keep\n * the dev path simple. When `apps/api` is wired, swapping back\n * to `httpBatchLink({ maxItems: 10 })` is a one-line change.\n */\nexport interface AnvilClientProviderProps {\n config: AnvilClientConfig;\n children: ReactNode;\n /**\n * Optional custom fetch for the in-process dev mock. When\n * provided, the tRPC client uses this fetch instead of the\n * real network. Production callers leave this undefined; the\n * `apps/web` dev bridge passes its own implementation.\n */\n devFetch?: typeof fetch;\n}\n\n/**\n * The Anvil tRPC client.\n *\n * History note: earlier versions of this provider used\n * `splitLink({ true: wsLink(...), false: httpBatchLink(...) })` so\n * subscription procedures would multiplex over a single WebSocket.\n * That setup opened a WebSocket on every mount, retried\n * indefinitely against `ws://localhost:3001/v1` (no real WS in dev),\n * spammed the console with \"ws connection error\", and held onto\n * EventSource handles that the GC couldn't free -- a noticeable\n * memory leak on the launcher tab.\n *\n * The router now has zero `subscription` procedures\n * (chat.subscribeChannel / social.presence.get are queries with\n * client-side polling, per Phase 5b / 6c). When real subscriptions\n * land, they should use a *dedicated* WS gateway with explicit\n * connect-on-subscribe semantics (tRPC v11 `createWSClient({ lazy: true })`)\n * so a tab with no active subscription never opens a socket.\n *\n * This scaffold uses `httpLink` with the default JSON transformer\n * (no superjson). For the dev renderer, a `devFetch` override\n * routes every request through the in-process mock registry in\n * `apps/web/src/lib/anvil-api/dev-fetch.ts`, bypassing the MSW\n * service worker entirely (which had wire-format issues with\n * tRPC v11's GET-batched queries).\n */\nexport function AnvilClientProvider({ config, children, devFetch }: AnvilClientProviderProps) {\n const [queryClient] = useState(\n () =>\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n }),\n );\n\n const [trpcClient] = useState(() =>\n trpc.createClient({\n links: [\n httpLink({\n url: config.baseUrl,\n headers: async () => {\n const token = await config.getToken();\n return token ? { authorization: `Bearer ${token}` } : {};\n },\n fetch: devFetch,\n }),\n ],\n }),\n );\n\n return (\n <trpc.Provider client={trpcClient} queryClient={queryClient}>\n <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>\n </trpc.Provider>\n );\n}\n\nexport const useAnvilClient = trpc.useContext;\n","import { trpc } from '../provider';\nimport { useEffect } from 'react';\n\nexport interface CurrentSession {\n valid: boolean;\n user?: { uuid: string; username: string; displayName: string; avatarUrl: string | null };\n expiresAt?: string;\n}\n\nexport const useCurrentSessionQuery = () => {\n // Real procedure doesn't exist yet (Phase 5). Return a stable shape for now.\n useEffect(() => {\n /* no-op */\n }, []);\n\n return {\n data: undefined as CurrentSession | undefined,\n isLoading: false,\n error: null,\n };\n};\n\n// Re-export the trpc client for use in custom hooks\nexport { trpc };\n","// React hooks for the Essential WS client. Kept OUT of `@anvil-js/client/ws`\n// so that the core WS client (AnvilWsClient + codec) stays React-free and\n// usable in plain Node scripts / bots. Only TYPES are imported from the ws\n// module here, so there is no runtime coupling.\n\nimport { useEffect, useState } from 'react';\nimport type { AnvilWsClient, AnvilWsEvents } from '../ws/client';\n\n/**\n * Subscribe to a WS event inside a React component. The subscription is\n * cleaned up automatically on unmount.\n */\nexport function useWsEvent<K extends keyof AnvilWsEvents>(\n client: AnvilWsClient,\n event: K,\n handler: (data: AnvilWsEvents[K]) => void,\n): void {\n useEffect(() => {\n const off = client.on(event, handler);\n return off;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, event]);\n}\n\n/**\n * Track connection state for a WS client\n * ('disconnected' | 'connecting' | 'connected' | 'ready').\n */\nexport function useWsState(client: AnvilWsClient): AnvilWsClient['state'] | 'disconnected' {\n const [state, setState] = useState<AnvilWsClient['state']>(\n client.isConnected() ? (client.isReady() ? 'ready' : 'connected') : 'disconnected',\n );\n useEffect(() => {\n const update = () =>\n setState(client.isConnected() ? (client.isReady() ? 'ready' : 'connected') : 'disconnected');\n const off1 = client.on('open', update);\n const off2 = client.on('bootstrap', update);\n const off3 = client.on('close', update);\n return () => {\n off1();\n off2();\n off3();\n };\n }, [client]);\n return state;\n}\n"]}