@parcae/sdk 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,8 +8,8 @@ interface ParcaeProviderProps {
8
8
  client?: ParcaeClient;
9
9
  /** API base URL (required if no client provided). */
10
10
  url?: string;
11
- /** Bearer token or null (pre-auth). */
12
- apiKey?: string | null;
11
+ /** Bearer token, null (no session), or undefined (still loading). */
12
+ apiKey?: string | null | undefined;
13
13
  /** Stable user ID — triggers re-auth when it changes. */
14
14
  userId?: string | null;
15
15
  /** API version. Default: "v1" */
@@ -20,26 +20,16 @@ interface ParcaeProviderProps {
20
20
  onReady?: (client: ParcaeClient) => void;
21
21
  onError?: (error: Error) => void;
22
22
  }
23
- /**
24
- * ParcaeProvider — creates the SDK client once and re-authenticates on userId change.
25
- *
26
- * Usage with pre-created client:
27
- * ```tsx
28
- * const client = createClient({ url: "...", transport: "socket" });
29
- * <ParcaeProvider client={client}><App /></ParcaeProvider>
30
- * ```
31
- *
32
- * Usage with inline config:
33
- * ```tsx
34
- * <ParcaeProvider url="http://localhost:3000" apiKey={token} userId={user.id}>
35
- * <App />
36
- * </ParcaeProvider>
37
- * ```
38
- */
39
23
  declare const ParcaeProvider: React__default.FC<ParcaeProviderProps>;
40
24
 
41
- declare const ParcaeContext: React.Context<ParcaeClient | null>;
42
- declare function useParcae(): ParcaeClient;
25
+ type AuthState = "loading" | "authenticated" | "unauthenticated";
26
+ interface ParcaeContextValue {
27
+ client: ParcaeClient;
28
+ authState: AuthState;
29
+ authVersion: number;
30
+ }
31
+ declare const ParcaeContext: React.Context<ParcaeContextValue | null>;
32
+ declare function useParcae(): ParcaeContextValue;
43
33
 
44
34
  interface QueryChain<T> {
45
35
  find(): Promise<T[]>;
@@ -49,13 +39,21 @@ interface QueryChain<T> {
49
39
  __adapter?: any;
50
40
  __debounceMs?: number;
51
41
  }
42
+ interface UseQueryOptions {
43
+ /**
44
+ * Wait for authentication to resolve before firing the query.
45
+ * Default: true — queries don't fire while auth is "loading".
46
+ * Set to false for public/unauthenticated queries.
47
+ */
48
+ waitForAuth?: boolean;
49
+ }
52
50
  interface UseQueryResult<T> {
53
51
  items: T[];
54
52
  loading: boolean;
55
53
  error: Error | null;
56
54
  refetch: () => void;
57
55
  }
58
- declare function useQuery<T>(chain: QueryChain<T> | null | undefined): UseQueryResult<T>;
56
+ declare function useQuery<T>(chain: QueryChain<T> | null | undefined, options?: UseQueryOptions): UseQueryResult<T>;
59
57
 
60
58
  declare function useApi(): {
61
59
  get: (path: string, data?: any) => Promise<any>;
@@ -69,15 +67,20 @@ declare function useApi(): {
69
67
  */
70
68
  declare function useSDK(): ParcaeClient;
71
69
  /**
72
- * useConnectionStatus — reactive connection state.
70
+ * useConnectionStatus — connection + auth state.
73
71
  */
74
72
  declare function useConnectionStatus(): {
75
73
  isConnected: boolean;
76
74
  isLoading: boolean;
75
+ authState: AuthState;
77
76
  };
77
+ /**
78
+ * useAuthState — just the auth state.
79
+ */
80
+ declare function useAuthState(): AuthState;
78
81
 
79
82
  declare function useSetting<T = string>(key: string, defaultValue: T): [T, (value: T) => Promise<void>, {
80
83
  isLoading: boolean;
81
84
  }];
82
85
 
83
- export { ParcaeContext, ParcaeProvider, type ParcaeProviderProps, useApi, useConnectionStatus, useParcae, useQuery, useSDK, useSetting };
86
+ export { type AuthState, ParcaeContext, type ParcaeContextValue, ParcaeProvider, type ParcaeProviderProps, useApi, useAuthState, useConnectionStatus, useParcae, useQuery, useSDK, useSetting };
@@ -1,14 +1,14 @@
1
1
  import { createClient } from '../chunk-XAFYMW5P.js';
2
- import { createContext, useContext, useMemo, useRef, useEffect, useCallback, useSyncExternalStore, useState } from 'react';
2
+ import { createContext, useContext, useState, useMemo, useRef, useEffect, useCallback, useSyncExternalStore } from 'react';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
5
5
  var ParcaeContext = createContext(null);
6
6
  function useParcae() {
7
- const client = useContext(ParcaeContext);
8
- if (!client) {
7
+ const ctx = useContext(ParcaeContext);
8
+ if (!ctx) {
9
9
  throw new Error("useParcae must be used within a <ParcaeProvider>");
10
10
  }
11
- return client;
11
+ return ctx;
12
12
  }
13
13
  var ParcaeProvider = ({
14
14
  client: externalClient,
@@ -21,6 +21,13 @@ var ParcaeProvider = ({
21
21
  onReady,
22
22
  onError
23
23
  }) => {
24
+ const [authState, setAuthState] = useState(
25
+ // If apiKey is undefined, the frontend auth provider is still loading.
26
+ // If apiKey is null, the user is not logged in.
27
+ // If apiKey is a string, we have a token but haven't verified it yet.
28
+ apiKey === void 0 ? "loading" : apiKey === null ? "unauthenticated" : "loading"
29
+ );
30
+ const [authVersion, setAuthVersion] = useState(0);
24
31
  const client = useMemo(() => {
25
32
  if (externalClient) return externalClient;
26
33
  if (!url)
@@ -29,15 +36,31 @@ var ParcaeProvider = ({
29
36
  );
30
37
  return createClient({ url, version, transport, key: null });
31
38
  }, [externalClient, url, version, transport]);
32
- const apiKeyRef = useRef(apiKey);
33
- apiKeyRef.current = apiKey;
34
39
  const onReadyRef = useRef(onReady);
35
40
  onReadyRef.current = onReady;
36
41
  const onErrorRef = useRef(onError);
37
42
  onErrorRef.current = onError;
38
43
  useEffect(() => {
39
- client.setKey(apiKeyRef.current ?? null).then(() => onReadyRef.current?.(client)).catch((err) => onErrorRef.current?.(err));
40
- }, [userId, client]);
44
+ if (apiKey === void 0) {
45
+ setAuthState("loading");
46
+ return;
47
+ }
48
+ if (apiKey === null) {
49
+ setAuthState("unauthenticated");
50
+ setAuthVersion((v) => v + 1);
51
+ return;
52
+ }
53
+ setAuthState("loading");
54
+ client.setKey(apiKey).then(() => {
55
+ setAuthState("authenticated");
56
+ setAuthVersion((v) => v + 1);
57
+ onReadyRef.current?.(client);
58
+ }).catch((err) => {
59
+ setAuthState("unauthenticated");
60
+ setAuthVersion((v) => v + 1);
61
+ onErrorRef.current?.(err);
62
+ });
63
+ }, [apiKey, userId, client]);
41
64
  useEffect(() => {
42
65
  const onErr = (err) => onErrorRef.current?.(err);
43
66
  client.on("error", onErr);
@@ -45,7 +68,11 @@ var ParcaeProvider = ({
45
68
  client.off("error", onErr);
46
69
  };
47
70
  }, [client]);
48
- return /* @__PURE__ */ jsx(ParcaeContext.Provider, { value: client, children });
71
+ const contextValue = useMemo(
72
+ () => ({ client, authState, authVersion }),
73
+ [client, authState, authVersion]
74
+ );
75
+ return /* @__PURE__ */ jsx(ParcaeContext.Provider, { value: contextValue, children });
49
76
  };
50
77
  var CACHE_TIMEOUT_MS = 6e4;
51
78
  var DEFAULT_DEBOUNCE_MS = 100;
@@ -55,11 +82,12 @@ function buildCacheKey(chain, authVersion) {
55
82
  const steps = JSON.stringify(chain.__steps ?? []);
56
83
  return `${type}:${authVersion}:${steps}`;
57
84
  }
58
- function useQuery(chain) {
59
- const client = useParcae();
60
- const authVersion = client.authVersion;
61
- const cacheKey = chain ? buildCacheKey(chain, authVersion) : "__null__";
62
- if (!queryCache.has(cacheKey) && chain) {
85
+ function useQuery(chain, options = {}) {
86
+ const { client, authState, authVersion } = useParcae();
87
+ const waitForAuth = options.waitForAuth ?? true;
88
+ const authReady = !waitForAuth || authState !== "loading";
89
+ const cacheKey = chain && authReady ? buildCacheKey(chain, authVersion) : "__null__";
90
+ if (!queryCache.has(cacheKey) && chain && authReady) {
63
91
  queryCache.set(cacheKey, {
64
92
  items: [],
65
93
  itemMap: /* @__PURE__ */ new Map(),
@@ -104,7 +132,7 @@ function useQuery(chain) {
104
132
  const getSnapshot = useCallback(() => entry?.stateVersion ?? 0, [entry]);
105
133
  useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
106
134
  const refetch = useCallback(() => {
107
- if (!chain || !entry) return;
135
+ if (!chain || !entry || !authReady) return;
108
136
  entry.loading = true;
109
137
  notifyListeners(entry);
110
138
  chain.find().then((items) => {
@@ -141,10 +169,14 @@ function useQuery(chain) {
141
169
  client.send("unsubscribe:query", { hash: cacheKey });
142
170
  };
143
171
  }
144
- }, [chain, entry, cacheKey, client]);
172
+ }, [chain, entry, cacheKey, client, authReady]);
145
173
  useEffect(() => {
146
- if (chain) refetch();
147
- }, [cacheKey]);
174
+ if (chain && authReady) refetch();
175
+ }, [cacheKey, authReady]);
176
+ if (!authReady) {
177
+ return { items: [], loading: true, error: null, refetch: () => {
178
+ } };
179
+ }
148
180
  if (!entry) {
149
181
  return { items: [], loading: false, error: null, refetch: () => {
150
182
  } };
@@ -189,7 +221,7 @@ function applyDiffOps(entry, chain) {
189
221
  const existing = entry.itemMap.get(op.id);
190
222
  if (existing && op.data) {
191
223
  for (const [key, value] of Object.entries(op.data)) {
192
- existing.__data[key] = value;
224
+ existing[key] = value;
193
225
  }
194
226
  changed = true;
195
227
  }
@@ -203,7 +235,7 @@ function applyDiffOps(entry, chain) {
203
235
  }
204
236
  }
205
237
  function useApi() {
206
- const client = useParcae();
238
+ const { client } = useParcae();
207
239
  return useMemo(
208
240
  () => ({
209
241
  get: client.get.bind(client),
@@ -216,20 +248,29 @@ function useApi() {
216
248
  );
217
249
  }
218
250
  function useSDK() {
219
- return useParcae();
251
+ return useParcae().client;
220
252
  }
221
253
  function useConnectionStatus() {
222
- const client = useParcae();
254
+ const { client, authState } = useParcae();
223
255
  return {
224
256
  isConnected: client.isConnected,
225
- isLoading: client.isLoading
257
+ isLoading: client.isLoading,
258
+ authState
226
259
  };
227
260
  }
261
+ function useAuthState() {
262
+ return useParcae().authState;
263
+ }
228
264
  function useSetting(key, defaultValue) {
229
- const client = useParcae();
265
+ const { client, authState } = useParcae();
230
266
  const [value, setValue] = useState(defaultValue);
231
267
  const [isLoading, setIsLoading] = useState(true);
232
268
  useEffect(() => {
269
+ if (authState === "loading") return;
270
+ if (authState === "unauthenticated") {
271
+ setIsLoading(false);
272
+ return;
273
+ }
233
274
  let cancelled = false;
234
275
  client.get(`/settings/${encodeURIComponent(key)}`).then((result) => {
235
276
  if (!cancelled && result?.value !== void 0) {
@@ -242,7 +283,7 @@ function useSetting(key, defaultValue) {
242
283
  return () => {
243
284
  cancelled = true;
244
285
  };
245
- }, [key, client]);
286
+ }, [key, client, authState]);
246
287
  const update = useCallback(
247
288
  async (newValue) => {
248
289
  setValue(newValue);
@@ -255,6 +296,6 @@ function useSetting(key, defaultValue) {
255
296
  return [value, update, { isLoading }];
256
297
  }
257
298
 
258
- export { ParcaeContext, ParcaeProvider, useApi, useConnectionStatus, useParcae, useQuery, useSDK, useSetting };
299
+ export { ParcaeContext, ParcaeProvider, useApi, useAuthState, useConnectionStatus, useParcae, useQuery, useSDK, useSetting };
259
300
  //# sourceMappingURL=index.js.map
260
301
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/context.ts","../../src/react/Provider.tsx","../../src/react/useQuery.ts","../../src/react/useApi.ts","../../src/react/useSetting.ts"],"names":["useEffect","useMemo","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { ParcaeClient } from \"../client\";\n\nexport const ParcaeContext = createContext<ParcaeClient | null>(null);\n\nexport function useParcae(): ParcaeClient {\n const client = useContext(ParcaeContext);\n if (!client) {\n throw new Error(\"useParcae must be used within a <ParcaeProvider>\");\n }\n return client;\n}\n","\"use client\";\n\nimport React, { useEffect, useMemo, useRef } from \"react\";\nimport { createClient } from \"../client\";\nimport type { ParcaeClient, ClientConfig } from \"../client\";\nimport { ParcaeContext } from \"./context\";\n\nexport interface ParcaeProviderProps {\n /** Pre-created client instance. If provided, url/key/transport are ignored. */\n client?: ParcaeClient;\n /** API base URL (required if no client provided). */\n url?: string;\n /** Bearer token or null (pre-auth). */\n apiKey?: string | null;\n /** Stable user ID — triggers re-auth when it changes. */\n userId?: string | null;\n /** API version. Default: \"v1\" */\n version?: string;\n /** Transport type. Default: \"socket\" */\n transport?: ClientConfig[\"transport\"];\n children: React.ReactNode;\n onReady?: (client: ParcaeClient) => void;\n onError?: (error: Error) => void;\n}\n\n/**\n * ParcaeProvider — creates the SDK client once and re-authenticates on userId change.\n *\n * Usage with pre-created client:\n * ```tsx\n * const client = createClient({ url: \"...\", transport: \"socket\" });\n * <ParcaeProvider client={client}><App /></ParcaeProvider>\n * ```\n *\n * Usage with inline config:\n * ```tsx\n * <ParcaeProvider url=\"http://localhost:3000\" apiKey={token} userId={user.id}>\n * <App />\n * </ParcaeProvider>\n * ```\n */\nexport const ParcaeProvider: React.FC<ParcaeProviderProps> = ({\n client: externalClient,\n url,\n apiKey,\n userId,\n version = \"v1\",\n transport = \"socket\",\n children,\n onReady,\n onError,\n}) => {\n // Create client once per url+version+transport (or use external)\n const client = useMemo(() => {\n if (externalClient) return externalClient;\n if (!url)\n throw new Error(\n \"ParcaeProvider requires either a `client` prop or a `url` prop\",\n );\n return createClient({ url, version, transport, key: null });\n }, [externalClient, url, version, transport]);\n\n // Refs for callbacks to avoid re-running effects on unstable inline functions\n const apiKeyRef = useRef(apiKey);\n apiKeyRef.current = apiKey;\n const onReadyRef = useRef(onReady);\n onReadyRef.current = onReady;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n // Re-authenticate when userId changes\n useEffect(() => {\n client\n .setKey(apiKeyRef.current ?? null)\n .then(() => onReadyRef.current?.(client))\n .catch((err) => onErrorRef.current?.(err));\n }, [userId, client]);\n\n // Forward transport errors\n useEffect(() => {\n const onErr = (err: Error) => onErrorRef.current?.(err);\n client.on(\"error\", onErr);\n return () => {\n client.off(\"error\", onErr);\n };\n }, [client]);\n\n return (\n <ParcaeContext.Provider value={client}>{children}</ParcaeContext.Provider>\n );\n};\n\nexport default ParcaeProvider;\n","\"use client\";\n\n/**\n * useQuery — reactive data fetching with realtime subscriptions.\n *\n * Takes a query chain, returns an array of typed model instances.\n * Subscribes to realtime updates — the server diffs queries on model changes\n * and pushes surgical add/remove/update ops.\n *\n * @example\n * ```tsx\n * const { items, loading } = useQuery(Post.where({ published: true }));\n * ```\n */\n\nimport { useCallback, useEffect, useSyncExternalStore } from \"react\";\nimport { useParcae } from \"./context\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\ninterface QueryChain<T> {\n find(): Promise<T[]>;\n __steps?: any[];\n __modelType?: string;\n __modelClass?: any;\n __adapter?: any;\n __debounceMs?: number;\n}\n\ninterface UseQueryResult<T> {\n items: T[];\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\ninterface DiffOp {\n op: \"add\" | \"remove\" | \"update\";\n id: string;\n data?: Record<string, any>;\n}\n\n// ─── Query Cache ─────────────────────────────────────────────────────────────\n\nconst CACHE_TIMEOUT_MS = 60_000;\nconst DEFAULT_DEBOUNCE_MS = 100;\n\ninterface CacheEntry<T = any> {\n items: T[];\n itemMap: Map<string, T>;\n loading: boolean;\n error: Error | null;\n refCount: number;\n timeoutHandle: ReturnType<typeof setTimeout> | null;\n version: number;\n stateVersion: number;\n listeners: Set<() => void>;\n subscriptionHash: string | null;\n pendingOps: DiffOp[];\n debounceTimer: ReturnType<typeof setTimeout> | null;\n debounceMs: number;\n disposeSubscription: (() => void) | null;\n}\n\nconst queryCache = new Map<string, CacheEntry>();\n\nfunction buildCacheKey(chain: QueryChain<any>, authVersion: number): string {\n const type = chain.__modelType ?? \"unknown\";\n const steps = JSON.stringify(chain.__steps ?? []);\n return `${type}:${authVersion}:${steps}`;\n}\n\n// ─── useQuery ────────────────────────────────────────────────────────────────\n\nexport function useQuery<T>(\n chain: QueryChain<T> | null | undefined,\n): UseQueryResult<T> {\n const client = useParcae();\n const authVersion = client.authVersion;\n\n const cacheKey = chain ? buildCacheKey(chain, authVersion) : \"__null__\";\n\n // ── Get or create cache entry ──────────────────────────────────────\n\n if (!queryCache.has(cacheKey) && chain) {\n queryCache.set(cacheKey, {\n items: [],\n itemMap: new Map(),\n loading: true,\n error: null,\n refCount: 0,\n timeoutHandle: null,\n version: 0,\n stateVersion: 0,\n listeners: new Set(),\n subscriptionHash: null,\n pendingOps: [],\n debounceTimer: null,\n debounceMs: chain.__debounceMs ?? DEFAULT_DEBOUNCE_MS,\n disposeSubscription: null,\n });\n }\n\n const entry = queryCache.get(cacheKey);\n\n // ── useSyncExternalStore for tear-safe rendering ───────────────────\n\n const subscribe = useCallback(\n (listener: () => void) => {\n if (!entry) return () => {};\n entry.listeners.add(listener);\n entry.refCount++;\n\n // Cancel pending GC\n if (entry.timeoutHandle) {\n clearTimeout(entry.timeoutHandle);\n entry.timeoutHandle = null;\n }\n\n return () => {\n entry.listeners.delete(listener);\n entry.refCount--;\n\n // GC: if no subscribers left, schedule cleanup\n if (entry.refCount <= 0) {\n entry.timeoutHandle = setTimeout(() => {\n entry.disposeSubscription?.();\n queryCache.delete(cacheKey);\n }, CACHE_TIMEOUT_MS);\n }\n };\n },\n [entry, cacheKey],\n );\n\n const getSnapshot = useCallback(() => entry?.stateVersion ?? 0, [entry]);\n\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // ── Fetch + subscribe ──────────────────────────────────────────────\n\n const refetch = useCallback(() => {\n if (!chain || !entry) return;\n\n entry.loading = true;\n notifyListeners(entry);\n\n chain\n .find()\n .then((items) => {\n entry.items = items;\n entry.itemMap = new Map(items.map((item: any) => [item.id, item]));\n entry.loading = false;\n entry.error = null;\n entry.version++;\n notifyListeners(entry);\n })\n .catch((err) => {\n entry.error = err;\n entry.loading = false;\n notifyListeners(entry);\n });\n\n // Set up realtime subscription if transport supports it\n if (!entry.disposeSubscription && chain.__modelType) {\n const subEvent = `query:${cacheKey}`;\n\n // Ask server to subscribe to this query\n client.send(\"subscribe:query\", {\n hash: cacheKey,\n modelType: chain.__modelType,\n steps: chain.__steps ?? [],\n });\n\n // Listen for diff ops from the server\n const dispose = client.subscribe(subEvent, (ops: DiffOp[]) => {\n if (!entry) return;\n entry.pendingOps.push(...ops);\n\n // Debounce application of ops\n if (entry.debounceTimer) clearTimeout(entry.debounceTimer);\n entry.debounceTimer = setTimeout(() => {\n applyDiffOps(entry, chain);\n entry.debounceTimer = null;\n }, entry.debounceMs);\n });\n\n entry.subscriptionHash = cacheKey;\n entry.disposeSubscription = () => {\n dispose();\n client.send(\"unsubscribe:query\", { hash: cacheKey });\n };\n }\n }, [chain, entry, cacheKey, client]);\n\n // Initial fetch on mount / chain change\n useEffect(() => {\n if (chain) refetch();\n }, [cacheKey]); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!entry) {\n return { items: [], loading: false, error: null, refetch: () => {} };\n }\n\n return {\n items: entry.items,\n loading: entry.loading,\n error: entry.error,\n refetch,\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction notifyListeners(entry: CacheEntry): void {\n entry.stateVersion++;\n for (const listener of entry.listeners) listener();\n}\n\nfunction applyDiffOps<T>(entry: CacheEntry<T>, chain: QueryChain<T>): void {\n const ops = entry.pendingOps.splice(0);\n if (!ops.length) return;\n\n const ModelClass = chain.__modelClass;\n const adapter = chain.__adapter;\n let changed = false;\n\n for (const op of ops) {\n switch (op.op) {\n case \"add\": {\n if (!entry.itemMap.has(op.id) && op.data && ModelClass && adapter) {\n const instance = new ModelClass(adapter, op.data);\n entry.items.push(instance);\n entry.itemMap.set(op.id, instance);\n changed = true;\n }\n break;\n }\n case \"remove\": {\n if (entry.itemMap.has(op.id)) {\n entry.items = entry.items.filter((item: any) => item.id !== op.id);\n entry.itemMap.delete(op.id);\n changed = true;\n }\n break;\n }\n case \"update\": {\n const existing = entry.itemMap.get(op.id) as any;\n if (existing && op.data) {\n for (const [key, value] of Object.entries(op.data)) {\n existing.__data[key] = value;\n }\n changed = true;\n }\n break;\n }\n }\n }\n\n if (changed) {\n entry.version++;\n notifyListeners(entry);\n }\n}\n","\"use client\";\n\n/**\n * useApi — pre-bound HTTP methods from the Parcae client.\n *\n * @example\n * ```tsx\n * const { get, post } = useApi();\n * const data = await get(\"/custom-endpoint\");\n * ```\n */\n\nimport { useMemo } from \"react\";\nimport { useParcae } from \"./context\";\n\nexport function useApi() {\n const client = useParcae();\n\n return useMemo(\n () => ({\n get: client.get.bind(client),\n post: client.post.bind(client),\n put: client.put.bind(client),\n patch: client.patch.bind(client),\n delete: client.delete.bind(client),\n }),\n [client],\n );\n}\n\n/**\n * useSDK — raw client instance.\n */\nexport function useSDK() {\n return useParcae();\n}\n\n/**\n * useConnectionStatus — reactive connection state.\n */\nexport function useConnectionStatus() {\n const client = useParcae();\n // This is a snapshot — for true reactivity, components should\n // listen to client.on(\"connected\"/\"disconnected\") events.\n return {\n isConnected: client.isConnected,\n isLoading: client.isLoading,\n };\n}\n","\"use client\";\n\n/**\n * useSetting — key-value user settings stored as a Setting model.\n *\n * @example\n * ```tsx\n * const [theme, setTheme, { isLoading }] = useSetting(\"theme\", \"light\");\n * ```\n */\n\nimport { useState, useEffect, useCallback } from \"react\";\nimport { useParcae } from \"./context\";\n\nexport function useSetting<T = string>(\n key: string,\n defaultValue: T,\n): [T, (value: T) => Promise<void>, { isLoading: boolean }] {\n const client = useParcae();\n const [value, setValue] = useState<T>(defaultValue);\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() => {\n let cancelled = false;\n\n client\n .get(`/settings/${encodeURIComponent(key)}`)\n .then((result) => {\n if (!cancelled && result?.value !== undefined) {\n setValue(result.value);\n }\n })\n .catch(() => {\n // Setting doesn't exist yet — use default\n })\n .finally(() => {\n if (!cancelled) setIsLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [key, client]);\n\n const update = useCallback(\n async (newValue: T) => {\n setValue(newValue);\n await client.put(`/settings/${encodeURIComponent(key)}`, {\n value: newValue,\n });\n },\n [key, client],\n );\n\n return [value, update, { isLoading }];\n}\n"]}
1
+ {"version":3,"sources":["../../src/react/context.ts","../../src/react/Provider.tsx","../../src/react/useQuery.ts","../../src/react/useApi.ts","../../src/react/useSetting.ts"],"names":["useCallback","useEffect","useMemo","useState"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { ParcaeClient } from \"../client\";\n\nexport type AuthState = \"loading\" | \"authenticated\" | \"unauthenticated\";\n\nexport interface ParcaeContextValue {\n client: ParcaeClient;\n authState: AuthState;\n authVersion: number;\n}\n\nexport const ParcaeContext = createContext<ParcaeContextValue | null>(null);\n\nexport function useParcae(): ParcaeContextValue {\n const ctx = useContext(ParcaeContext);\n if (!ctx) {\n throw new Error(\"useParcae must be used within a <ParcaeProvider>\");\n }\n return ctx;\n}\n","\"use client\";\n\nimport React, {\n useEffect,\n useMemo,\n useRef,\n useState,\n useCallback,\n} from \"react\";\nimport { createClient } from \"../client\";\nimport type { ParcaeClient, ClientConfig } from \"../client\";\nimport { ParcaeContext } from \"./context\";\nimport type { ParcaeContextValue, AuthState } from \"./context\";\n\nexport interface ParcaeProviderProps {\n /** Pre-created client instance. If provided, url/key/transport are ignored. */\n client?: ParcaeClient;\n /** API base URL (required if no client provided). */\n url?: string;\n /** Bearer token, null (no session), or undefined (still loading). */\n apiKey?: string | null | undefined;\n /** Stable user ID — triggers re-auth when it changes. */\n userId?: string | null;\n /** API version. Default: \"v1\" */\n version?: string;\n /** Transport type. Default: \"socket\" */\n transport?: ClientConfig[\"transport\"];\n children: React.ReactNode;\n onReady?: (client: ParcaeClient) => void;\n onError?: (error: Error) => void;\n}\n\nexport const ParcaeProvider: React.FC<ParcaeProviderProps> = ({\n client: externalClient,\n url,\n apiKey,\n userId,\n version = \"v1\",\n transport = \"socket\",\n children,\n onReady,\n onError,\n}) => {\n const [authState, setAuthState] = useState<AuthState>(\n // If apiKey is undefined, the frontend auth provider is still loading.\n // If apiKey is null, the user is not logged in.\n // If apiKey is a string, we have a token but haven't verified it yet.\n apiKey === undefined\n ? \"loading\"\n : apiKey === null\n ? \"unauthenticated\"\n : \"loading\",\n );\n const [authVersion, setAuthVersion] = useState(0);\n\n const client = useMemo(() => {\n if (externalClient) return externalClient;\n if (!url)\n throw new Error(\n \"ParcaeProvider requires either a `client` prop or a `url` prop\",\n );\n return createClient({ url, version, transport, key: null });\n }, [externalClient, url, version, transport]);\n\n const onReadyRef = useRef(onReady);\n onReadyRef.current = onReady;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n // Authenticate when apiKey changes\n useEffect(() => {\n // apiKey is undefined → frontend auth still loading, do nothing\n if (apiKey === undefined) {\n setAuthState(\"loading\");\n return;\n }\n\n // apiKey is null → user is not logged in\n if (apiKey === null) {\n setAuthState(\"unauthenticated\");\n setAuthVersion((v) => v + 1);\n return;\n }\n\n // apiKey is a string → send to backend for verification\n setAuthState(\"loading\");\n client\n .setKey(apiKey)\n .then(() => {\n setAuthState(\"authenticated\");\n setAuthVersion((v) => v + 1);\n onReadyRef.current?.(client);\n })\n .catch((err) => {\n setAuthState(\"unauthenticated\");\n setAuthVersion((v) => v + 1);\n onErrorRef.current?.(err);\n });\n }, [apiKey, userId, client]);\n\n useEffect(() => {\n const onErr = (err: Error) => onErrorRef.current?.(err);\n client.on(\"error\", onErr);\n return () => {\n client.off(\"error\", onErr);\n };\n }, [client]);\n\n const contextValue = useMemo<ParcaeContextValue>(\n () => ({ client, authState, authVersion }),\n [client, authState, authVersion],\n );\n\n return (\n <ParcaeContext.Provider value={contextValue}>\n {children}\n </ParcaeContext.Provider>\n );\n};\n\nexport default ParcaeProvider;\n","\"use client\";\n\n/**\n * useQuery — reactive data fetching with realtime subscriptions.\n *\n * @example\n * ```tsx\n * const { items, loading } = useQuery(Post.where({ published: true }));\n *\n * // Wait for auth before firing (default: true)\n * const { items } = useQuery(query, { waitForAuth: true });\n * ```\n */\n\nimport { useCallback, useEffect, useSyncExternalStore } from \"react\";\nimport { useParcae } from \"./context\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\ninterface QueryChain<T> {\n find(): Promise<T[]>;\n __steps?: any[];\n __modelType?: string;\n __modelClass?: any;\n __adapter?: any;\n __debounceMs?: number;\n}\n\ninterface UseQueryOptions {\n /**\n * Wait for authentication to resolve before firing the query.\n * Default: true — queries don't fire while auth is \"loading\".\n * Set to false for public/unauthenticated queries.\n */\n waitForAuth?: boolean;\n}\n\ninterface UseQueryResult<T> {\n items: T[];\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\ninterface DiffOp {\n op: \"add\" | \"remove\" | \"update\";\n id: string;\n data?: Record<string, any>;\n}\n\n// ─── Query Cache ─────────────────────────────────────────────────────────────\n\nconst CACHE_TIMEOUT_MS = 60_000;\nconst DEFAULT_DEBOUNCE_MS = 100;\n\ninterface CacheEntry<T = any> {\n items: T[];\n itemMap: Map<string, T>;\n loading: boolean;\n error: Error | null;\n refCount: number;\n timeoutHandle: ReturnType<typeof setTimeout> | null;\n version: number;\n stateVersion: number;\n listeners: Set<() => void>;\n subscriptionHash: string | null;\n pendingOps: DiffOp[];\n debounceTimer: ReturnType<typeof setTimeout> | null;\n debounceMs: number;\n disposeSubscription: (() => void) | null;\n}\n\nconst queryCache = new Map<string, CacheEntry>();\n\nfunction buildCacheKey(chain: QueryChain<any>, authVersion: number): string {\n const type = chain.__modelType ?? \"unknown\";\n const steps = JSON.stringify(chain.__steps ?? []);\n return `${type}:${authVersion}:${steps}`;\n}\n\n// ─── useQuery ────────────────────────────────────────────────────────────────\n\nexport function useQuery<T>(\n chain: QueryChain<T> | null | undefined,\n options: UseQueryOptions = {},\n): UseQueryResult<T> {\n const { client, authState, authVersion } = useParcae();\n const waitForAuth = options.waitForAuth ?? true;\n\n // If waiting for auth and auth is still loading, return empty loading state\n const authReady = !waitForAuth || authState !== \"loading\";\n\n const cacheKey =\n chain && authReady ? buildCacheKey(chain, authVersion) : \"__null__\";\n\n // ── Get or create cache entry ──────────────────────────────────────\n\n if (!queryCache.has(cacheKey) && chain && authReady) {\n queryCache.set(cacheKey, {\n items: [],\n itemMap: new Map(),\n loading: true,\n error: null,\n refCount: 0,\n timeoutHandle: null,\n version: 0,\n stateVersion: 0,\n listeners: new Set(),\n subscriptionHash: null,\n pendingOps: [],\n debounceTimer: null,\n debounceMs: chain.__debounceMs ?? DEFAULT_DEBOUNCE_MS,\n disposeSubscription: null,\n });\n }\n\n const entry = queryCache.get(cacheKey);\n\n // ── useSyncExternalStore for tear-safe rendering ───────────────────\n\n const subscribe = useCallback(\n (listener: () => void) => {\n if (!entry) return () => {};\n entry.listeners.add(listener);\n entry.refCount++;\n\n if (entry.timeoutHandle) {\n clearTimeout(entry.timeoutHandle);\n entry.timeoutHandle = null;\n }\n\n return () => {\n entry.listeners.delete(listener);\n entry.refCount--;\n\n if (entry.refCount <= 0) {\n entry.timeoutHandle = setTimeout(() => {\n entry.disposeSubscription?.();\n queryCache.delete(cacheKey);\n }, CACHE_TIMEOUT_MS);\n }\n };\n },\n [entry, cacheKey],\n );\n\n const getSnapshot = useCallback(() => entry?.stateVersion ?? 0, [entry]);\n\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // ── Fetch + subscribe ──────────────────────────────────────────────\n\n const refetch = useCallback(() => {\n if (!chain || !entry || !authReady) return;\n\n entry.loading = true;\n notifyListeners(entry);\n\n chain\n .find()\n .then((items) => {\n entry.items = items;\n entry.itemMap = new Map(items.map((item: any) => [item.id, item]));\n entry.loading = false;\n entry.error = null;\n entry.version++;\n notifyListeners(entry);\n })\n .catch((err) => {\n entry.error = err;\n entry.loading = false;\n notifyListeners(entry);\n });\n\n // Set up realtime subscription\n if (!entry.disposeSubscription && chain.__modelType) {\n const subEvent = `query:${cacheKey}`;\n\n client.send(\"subscribe:query\", {\n hash: cacheKey,\n modelType: chain.__modelType,\n steps: chain.__steps ?? [],\n });\n\n const dispose = client.subscribe(subEvent, (ops: DiffOp[]) => {\n if (!entry) return;\n entry.pendingOps.push(...ops);\n\n if (entry.debounceTimer) clearTimeout(entry.debounceTimer);\n entry.debounceTimer = setTimeout(() => {\n applyDiffOps(entry, chain);\n entry.debounceTimer = null;\n }, entry.debounceMs);\n });\n\n entry.subscriptionHash = cacheKey;\n entry.disposeSubscription = () => {\n dispose();\n client.send(\"unsubscribe:query\", { hash: cacheKey });\n };\n }\n }, [chain, entry, cacheKey, client, authReady]);\n\n // Fetch when auth becomes ready or cache key changes\n useEffect(() => {\n if (chain && authReady) refetch();\n }, [cacheKey, authReady]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Auth not ready — return loading\n if (!authReady) {\n return { items: [], loading: true, error: null, refetch: () => {} };\n }\n\n if (!entry) {\n return { items: [], loading: false, error: null, refetch: () => {} };\n }\n\n return {\n items: entry.items,\n loading: entry.loading,\n error: entry.error,\n refetch,\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction notifyListeners(entry: CacheEntry): void {\n entry.stateVersion++;\n for (const listener of entry.listeners) listener();\n}\n\nfunction applyDiffOps<T>(entry: CacheEntry<T>, chain: QueryChain<T>): void {\n const ops = entry.pendingOps.splice(0);\n if (!ops.length) return;\n\n const ModelClass = chain.__modelClass;\n const adapter = chain.__adapter;\n let changed = false;\n\n for (const op of ops) {\n switch (op.op) {\n case \"add\": {\n if (!entry.itemMap.has(op.id) && op.data && ModelClass && adapter) {\n const instance = new ModelClass(adapter, op.data);\n entry.items.push(instance);\n entry.itemMap.set(op.id, instance);\n changed = true;\n }\n break;\n }\n case \"remove\": {\n if (entry.itemMap.has(op.id)) {\n entry.items = entry.items.filter((item: any) => item.id !== op.id);\n entry.itemMap.delete(op.id);\n changed = true;\n }\n break;\n }\n case \"update\": {\n const existing = entry.itemMap.get(op.id) as any;\n if (existing && op.data) {\n for (const [key, value] of Object.entries(op.data)) {\n existing[key] = value;\n }\n changed = true;\n }\n break;\n }\n }\n }\n\n if (changed) {\n entry.version++;\n notifyListeners(entry);\n }\n}\n","\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useParcae } from \"./context\";\n\nexport function useApi() {\n const { client } = useParcae();\n\n return useMemo(\n () => ({\n get: client.get.bind(client),\n post: client.post.bind(client),\n put: client.put.bind(client),\n patch: client.patch.bind(client),\n delete: client.delete.bind(client),\n }),\n [client],\n );\n}\n\n/**\n * useSDK — raw client instance.\n */\nexport function useSDK() {\n return useParcae().client;\n}\n\n/**\n * useConnectionStatus — connection + auth state.\n */\nexport function useConnectionStatus() {\n const { client, authState } = useParcae();\n return {\n isConnected: client.isConnected,\n isLoading: client.isLoading,\n authState,\n };\n}\n\n/**\n * useAuthState — just the auth state.\n */\nexport function useAuthState() {\n return useParcae().authState;\n}\n","\"use client\";\n\nimport { useState, useEffect, useCallback } from \"react\";\nimport { useParcae } from \"./context\";\n\nexport function useSetting<T = string>(\n key: string,\n defaultValue: T,\n): [T, (value: T) => Promise<void>, { isLoading: boolean }] {\n const { client, authState } = useParcae();\n const [value, setValue] = useState<T>(defaultValue);\n const [isLoading, setIsLoading] = useState(true);\n\n // Wait for auth before fetching settings (they're user-scoped)\n useEffect(() => {\n if (authState === \"loading\") return;\n if (authState === \"unauthenticated\") {\n setIsLoading(false);\n return;\n }\n\n let cancelled = false;\n\n client\n .get(`/settings/${encodeURIComponent(key)}`)\n .then((result) => {\n if (!cancelled && result?.value !== undefined) {\n setValue(result.value);\n }\n })\n .catch(() => {})\n .finally(() => {\n if (!cancelled) setIsLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [key, client, authState]);\n\n const update = useCallback(\n async (newValue: T) => {\n setValue(newValue);\n await client.put(`/settings/${encodeURIComponent(key)}`, {\n value: newValue,\n });\n },\n [key, client],\n );\n\n return [value, update, { isLoading }];\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcae/sdk",
3
- "version": "0.2.7",
3
+ "version": "0.3.0",
4
4
  "description": "Parcae SDK — client transport and React hooks for Parcae backends",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",