@vira-ui/react 1.1.1 → 2.0.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.
package/dist/index.js CHANGED
@@ -90,24 +90,6 @@ function useViraState(channel, initialOrOptions) {
90
90
  if (disablePooling) return null;
91
91
  return (0, import_core.getViraConnectionPool)({ url: apiUrl, authToken, debug });
92
92
  }, [disablePooling, apiUrl, authToken, debug]);
93
- const loadInitialData = (0, import_react.useCallback)(async () => {
94
- if (channel === "inventoryitem:") {
95
- const maxRetries = 30;
96
- let retries = 0;
97
- const trySendRequest = () => {
98
- if (transportRef.current && retries < maxRetries) {
99
- transportRef.current.sendEvent("inventoryitem.list", {});
100
- return;
101
- } else if (retries >= maxRetries) {
102
- } else {
103
- retries++;
104
- setTimeout(trySendRequest, 100);
105
- }
106
- };
107
- trySendRequest();
108
- } else {
109
- }
110
- }, [channel]);
111
93
  (0, import_react.useEffect)(() => {
112
94
  if (!channel) return;
113
95
  const handleMessage = (msg) => {
@@ -115,38 +97,6 @@ function useViraState(channel, initialOrOptions) {
115
97
  case "update":
116
98
  case "event":
117
99
  if (msg.channel === channel) {
118
- if (channel === "inventoryitem:" && msg.data && typeof msg.data === "object" && !msg.data.type && !msg.data.patch && !data) {
119
- setData(msg.data);
120
- return;
121
- }
122
- if (msg.data && typeof msg.data === "object" && msg.data.type === "diff" && msg.data.patch) {
123
- setData((prev) => {
124
- if (!prev) {
125
- return msg.data.patch;
126
- }
127
- if (typeof prev === "object" && typeof msg.data.patch === "object") {
128
- let newData = { ...prev };
129
- for (const [itemId, itemData] of Object.entries(msg.data.patch)) {
130
- if (itemData && typeof itemData === "object" && itemId !== "type" && itemId !== "patch") {
131
- newData[itemId] = itemData;
132
- }
133
- }
134
- return newData;
135
- }
136
- return prev;
137
- });
138
- return;
139
- }
140
- if (msg.data && typeof msg.data === "object" && msg.data.type === "event") {
141
- return;
142
- }
143
- if (msg.data && typeof msg.data === "object" && msg.data.patch) {
144
- return;
145
- }
146
- if (msg.data && typeof msg.data === "object" && msg.data.type === "inventory_list") {
147
- setData(msg.data.items);
148
- return;
149
- }
150
100
  setData((prev) => {
151
101
  if (!prev) {
152
102
  return msg.data;
@@ -165,29 +115,25 @@ function useViraState(channel, initialOrOptions) {
165
115
  }
166
116
  break;
167
117
  case "diff":
168
- if (msg.channel === channel) {
118
+ if (msg.channel === channel && msg.patch) {
169
119
  setData((prev) => {
170
- if (!prev || !msg.patch || typeof msg.patch === "object" && (msg.patch.type === "diff" || msg.patch.type === "event")) {
171
- return prev;
172
- }
173
120
  if (!prev) {
174
- return null;
121
+ return msg.patch;
175
122
  }
176
123
  if (typeof prev === "object" && typeof msg.patch === "object") {
177
- let newData = { ...prev };
178
- for (const [itemId, itemData] of Object.entries(msg.patch)) {
179
- if (itemData && typeof itemData === "object" && itemId !== "type" && itemId !== "patch") {
180
- newData[itemId] = itemData;
181
- }
124
+ let mergedData;
125
+ if (useDeepMerge) {
126
+ mergedData = (0, import_core.deepMerge)(prev, msg.patch);
127
+ } else {
128
+ mergedData = { ...prev, ...msg.patch };
182
129
  }
183
- return newData;
130
+ return mergedData;
184
131
  }
185
132
  return prev;
186
133
  });
187
134
  }
188
135
  break;
189
136
  }
190
- ;
191
137
  };
192
138
  if (pool) {
193
139
  const unsubChannel = pool.subscribe(channel, handleMessage);
@@ -196,7 +142,6 @@ function useViraState(channel, initialOrOptions) {
196
142
  setIsConnected(status.connected);
197
143
  if (status.connected && !wasConnectedRef.current) {
198
144
  wasConnectedRef.current = true;
199
- loadInitialData();
200
145
  onOpen?.();
201
146
  }
202
147
  if (!status.connected && wasConnectedRef.current) {
@@ -231,7 +176,6 @@ function useViraState(channel, initialOrOptions) {
231
176
  setError(null);
232
177
  if (!wasConnectedRef.current) {
233
178
  wasConnectedRef.current = true;
234
- loadInitialData();
235
179
  }
236
180
  onOpen?.();
237
181
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/useViraState.ts"],"sourcesContent":["/**\n * @vira-ui/react\n * \n * Vira Framework - React hooks for Vira Reactive Protocol\n * \n * This package provides React hooks for VRP, built on top of @vira-ui/core.\n */\n\nexport {\n useViraState,\n useViraStream,\n} from './useViraState';\nexport type {\n UseViraStateOptions,\n} from './useViraState';\n\n","import { useEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport {\n createViraClient,\n deepMerge,\n getViraConnectionPool,\n type ViraClient,\n type Message,\n type ViraConnectionPool,\n type ViraPoolStatus,\n} from '@vira-ui/core';\n\nexport interface UseViraStateOptions<T = any> {\n /** Initial value for the state */\n initial?: T | null;\n /** Enable msgId support for idempotency */\n enableMsgId?: boolean;\n /** Callback when connection opens */\n onOpen?: () => void;\n /** Callback when connection closes */\n onClose?: (event: CloseEvent) => void;\n /** Callback when connection error occurs */\n onError?: (error: Error) => void;\n /** Use deep merge for diff patches (default: true) */\n deepMerge?: boolean;\n /** API URL (defaults to VITE_API_URL env or 'http://localhost:8080') */\n apiUrl?: string;\n /** Auth token for handshake */\n authToken?: string;\n /** Disable connection pooling (fallback to 1 WS per channel). Default: false */\n disablePooling?: boolean;\n /** Enable debug logs for VRP (console.debug). Default: env VITE_VRP_DEBUG === 'true' */\n debug?: boolean;\n}\n\n/**\n * Unified hook for Vira Reactive Protocol state management.\n * Replaces both useViraState and useViraStream.\n *\n * @example\n * ```tsx\n * // Basic usage\n * const { data, sendUpdate } = useViraState<MyType>('my-channel');\n *\n * // With options\n * const { data, sendUpdate, sendDiff } = useViraState<User>('user:123', {\n * initial: { name: 'Guest' },\n * enableMsgId: true,\n * onOpen: () => console.log('Connected'),\n * deepMerge: true\n * });\n * ```\n */\nexport function useViraState<T = any, C extends string = string>(\n channel: C,\n initialOrOptions?: T | null | UseViraStateOptions<T>\n): {\n /** Current state data */\n data: T | null;\n /** Send an event to the server */\n sendEvent: (name: string, payload: any, msgId?: string) => void;\n /** Send a full update (replaces state) */\n sendUpdate: (payload: T, msgId?: string) => void;\n /** Send a partial diff (merges with current state) */\n sendDiff: (patch: Partial<T>, msgId?: string) => void;\n /** Connection status */\n isConnected: boolean;\n /** Connection error, if any */\n error: Error | null;\n} {\n // Parse options (backward compatibility: second param can be initial value or options)\n const options: UseViraStateOptions<T> = useMemo(() => {\n if (initialOrOptions === null || initialOrOptions === undefined) {\n return {};\n }\n // If it's an object with known option keys, treat as options\n if (\n typeof initialOrOptions === 'object' &&\n !Array.isArray(initialOrOptions) &&\n ('enableMsgId' in initialOrOptions ||\n 'onOpen' in initialOrOptions ||\n 'onClose' in initialOrOptions ||\n 'onError' in initialOrOptions ||\n 'deepMerge' in initialOrOptions ||\n 'initial' in initialOrOptions ||\n 'apiUrl' in initialOrOptions ||\n 'authToken' in initialOrOptions ||\n 'disablePooling' in initialOrOptions ||\n 'debug' in initialOrOptions)\n ) {\n return initialOrOptions as UseViraStateOptions<T>;\n }\n // Otherwise, treat as initial value (backward compatibility)\n return { initial: initialOrOptions as T | null };\n }, [initialOrOptions]);\n\n const {\n initial = null,\n enableMsgId = false,\n onOpen,\n onClose,\n onError,\n deepMerge: useDeepMerge = true,\n apiUrl: apiUrlOption,\n authToken: authTokenOption,\n disablePooling = false,\n debug: debugOption,\n } = options;\n\n const [data, setData] = useState<T | null>(initial);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n type Transport = {\n sendEvent: (name: string, payload: any, msgId?: string) => void;\n sendUpdate: (payload: T, msgId?: string) => void;\n sendDiff: (patch: Partial<T>, msgId?: string) => void;\n };\n\n const transportRef = useRef<Transport | null>(null);\n const clientRef = useRef<ViraClient | null>(null); // legacy non-pooled mode only\n const sessionRef = useRef<string | null>(null);\n const msgIdCounterRef = useRef(0);\n const wasConnectedRef = useRef(false);\n const lastErrorRef = useRef<Error | null>(null);\n\n // Use provided apiUrl or fallback to env or default\n // Note: import.meta is only available in ESM, so we check safely\n const apiUrl = useMemo(() => {\n if (apiUrlOption) return apiUrlOption;\n // Try to get from env if available (Vite/bundler environment)\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n if (env?.VITE_API_URL) return env.VITE_API_URL;\n } catch {\n // Ignore if import.meta is not available\n }\n return 'http://localhost:8080';\n }, [apiUrlOption]);\n\n // Get authToken from options or try to get from env\n const authToken = useMemo(() => {\n if (authTokenOption !== undefined) return authTokenOption;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n return env?.VITE_AUTH_TOKEN || '';\n } catch {\n return '';\n }\n }, [authTokenOption]);\n\n const debug = useMemo(() => {\n if (debugOption !== undefined) return Boolean(debugOption);\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n return String(env?.VITE_VRP_DEBUG || '').toLowerCase() === 'true';\n } catch {\n return false;\n }\n }, [debugOption]);\n\n const pool: ViraConnectionPool | null = useMemo(() => {\n if (disablePooling) return null;\n return getViraConnectionPool({ url: apiUrl, authToken, debug });\n }, [disablePooling, apiUrl, authToken, debug]);\n\n // ДОБАВИТЬ ЭТУ ФУНКЦИЮ:\n const loadInitialData = useCallback(async () => {\n\n\n // Если это канал товаров, ждем транспорт и запрашиваем данные\n if (channel === 'inventoryitem:') {\n\n // Ждем до 3 секунд пока транспорт будет готов\n const maxRetries = 30;\n let retries = 0;\n\n const trySendRequest = () => {\n if (transportRef.current && retries < maxRetries) {\n transportRef.current.sendEvent('inventoryitem.list', {});\n return;\n } else if (retries >= maxRetries) {\n } else {\n retries++;\n setTimeout(trySendRequest, 100);\n }\n };\n\n trySendRequest();\n } else {\n }\n }, [channel]);\n\n\n\n\n useEffect(() => {\n if (!channel) return;\n\n const handleMessage = (msg: Message) => {\n\n switch (msg.type) {\n case 'update':\n case 'event':\n if (msg.channel === channel) {\n // НОВАЯ ЛОГИКА: Если это первое update с полными данными товаров\n if (channel === 'inventoryitem:' && msg.data && typeof msg.data === 'object' &&\n !msg.data.type && !msg.data.patch && !data) {\n setData(msg.data as T);\n return;\n }\n\n // Handle diff patches that come as update messages\n if (msg.data && typeof msg.data === 'object' && msg.data.type === 'diff' && msg.data.patch) {\n\n setData((prev) => {\n\n if (!prev) {\n return msg.data.patch as T;\n }\n\n // Merge diff данных с существующими данными\n if (typeof prev === 'object' && typeof msg.data.patch === 'object') {\n let newData = { ...(prev as any) };\n\n // Обновляем только элементы из diff patch\n for (const [itemId, itemData] of Object.entries(msg.data.patch)) {\n if (itemData && typeof itemData === 'object' && itemId !== 'type' && itemId !== 'patch') {\n newData[itemId] = itemData;\n }\n }\n\n return newData as T;\n }\n\n return prev;\n });\n return;\n }\n\n // Handle events that come as update messages (ignore service events)\n if (msg.data && typeof msg.data === 'object' && msg.data.type === 'event') {\n return;\n }\n\n // ПРОВЕРКА: Игнорируем другие служебные сообщения\n if (msg.data && typeof msg.data === 'object' && msg.data.patch) {\n return; // Выходим без обновления данных\n }\n\n // Обработка запроса начальных данных\n if (msg.data && typeof msg.data === 'object' && msg.data.type === 'inventory_list') {\n setData(msg.data.items as T);\n return;\n }\n\n\n setData((prev) => {\n\n if (!prev) {\n return msg.data as T;\n }\n\n if (typeof prev === 'object' && typeof msg.data === 'object') {\n let mergedData;\n if (useDeepMerge) {\n mergedData = deepMerge(prev as Record<string, any>, msg.data as Record<string, any>) as T;\n } else {\n mergedData = { ...(prev as any), ...(msg.data as any) };\n }\n return mergedData;\n }\n\n return msg.data as T;\n });\n }\n break;\n\n\n case 'diff':\n if (msg.channel === channel) {\n // СПЕЦИАЛЬНАЯ ЛОГИКА для diff - только для товаров\n setData((prev) => {\n\n // Игнорируем служебные diff (с type: 'diff')\n if (!prev || !msg.patch ||\n (typeof msg.patch === 'object' && (msg.patch.type === 'diff' || msg.patch.type === 'event'))) {\n return prev;\n }\n\n if (!prev) {\n return null; // Возвращаем null для соответствия типу T | null\n }\n\n // Merge diff данных с существующими товарами\n if (typeof prev === 'object' && typeof msg.patch === 'object') {\n let newData = { ...(prev as any) };\n\n // Обновляем только товары из diff\n for (const [itemId, itemData] of Object.entries(msg.patch)) {\n if (itemData && typeof itemData === 'object' && itemId !== 'type' && itemId !== 'patch') {\n newData[itemId] = itemData;\n }\n }\n\n return newData as T;\n }\n\n return prev;\n });\n }\n break;\n };\n };\n\n // --- Pooled mode (default) ---\n if (pool) {\n // Subscribe to messages for this channel\n const unsubChannel = pool.subscribe(channel, handleMessage);\n\n // Track shared connection status\n const unsubStatus = pool.onStatus((status: ViraPoolStatus) => {\n setError(status.error);\n setIsConnected(status.connected);\n\n // Fire callbacks only on transitions\n if (status.connected && !wasConnectedRef.current) {\n wasConnectedRef.current = true;\n loadInitialData();\n onOpen?.();\n }\n if (!status.connected && wasConnectedRef.current) {\n wasConnectedRef.current = false;\n // We don't get a real CloseEvent from pooled status, so we pass a lightweight synthetic object\n const synthetic = (typeof CloseEvent !== 'undefined'\n ? new CloseEvent('close', { code: 1001, reason: 'pooled disconnect' })\n : ({ code: 1001, reason: 'pooled disconnect' } as any)) as any;\n onClose?.(synthetic);\n }\n if (status.error) {\n if (lastErrorRef.current !== status.error) {\n lastErrorRef.current = status.error;\n onError?.(status.error);\n }\n } else {\n lastErrorRef.current = null;\n }\n });\n\n // Wire transport for send* APIs\n transportRef.current = {\n sendEvent: (name, payload, msgId) => pool.sendEvent(channel, name, payload, msgId),\n sendUpdate: (payload, msgId) => pool.sendUpdate(channel, payload, msgId),\n sendDiff: (patch, msgId) => pool.sendDiff(channel, patch, msgId),\n };\n\n return () => {\n unsubStatus();\n unsubChannel();\n transportRef.current = null;\n setIsConnected(false);\n setError(null);\n };\n }\n\n // --- Legacy mode (1 WS per channel) ---\n const handleConnect = () => {\n setIsConnected(true);\n setError(null);\n if (!wasConnectedRef.current) {\n wasConnectedRef.current = true;\n loadInitialData();\n }\n onOpen?.();\n };\n\n const handleDisconnect = (event?: CloseEvent) => {\n setIsConnected(false);\n wasConnectedRef.current = false;\n onClose?.(event!);\n };\n\n const handleError = (err: Error) => {\n setError(err);\n onError?.(err);\n };\n\n const client = createViraClient({\n url: apiUrl,\n channel,\n onMessage: handleMessage,\n onConnect: handleConnect,\n onDisconnect: handleDisconnect,\n onError: handleError,\n session: sessionRef.current,\n authToken,\n onSessionChange: (newSession: string | null) => {\n sessionRef.current = newSession;\n },\n });\n\n clientRef.current = client;\n transportRef.current = {\n sendEvent: (name, payload, msgId) => client.sendEvent(name, payload, msgId),\n sendUpdate: (payload, msgId) => client.sendUpdate(payload, msgId),\n sendDiff: (patch, msgId) => client.sendDiff(patch, msgId),\n };\n\n return () => {\n client.close();\n clientRef.current = null;\n transportRef.current = null;\n setIsConnected(false);\n setError(null);\n };\n }, [channel, apiUrl, authToken, onOpen, onClose, onError, useDeepMerge, pool]);\n\n // Generate msgId if enabled\n const generateMsgId = useCallback((): string | undefined => {\n if (!enableMsgId) return undefined;\n msgIdCounterRef.current++;\n return `${channel}:${Date.now()}:${msgIdCounterRef.current}`;\n }, [channel, enableMsgId]);\n\n const sendEvent = useCallback(\n (name: string, payload: any, msgId?: string) => {\n transportRef.current?.sendEvent(name, payload, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n const sendUpdate = useCallback(\n (payload: T, msgId?: string) => {\n transportRef.current?.sendUpdate(payload, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n const sendDiff = useCallback(\n (patch: Partial<T>, msgId?: string) => {\n transportRef.current?.sendDiff(patch, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n return {\n data,\n sendEvent,\n sendUpdate,\n sendDiff,\n isConnected,\n error,\n };\n}\n\n/**\n * Legacy hook - use useViraState instead.\n * @deprecated Use useViraState with options instead\n */\nexport function useViraStream<T = any, C extends string = string>(\n channel: C,\n options?: UseViraStateOptions<T>\n) {\n return useViraState<T, C>(channel, { ...options, initial: null });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/useViraState.ts"],"sourcesContent":["/**\n * @vira-ui/react\n * \n * Vira Framework - React hooks for Vira Reactive Protocol\n * \n * This package provides React hooks for VRP, built on top of @vira-ui/core.\n */\n\nexport {\n useViraState,\n useViraStream,\n} from './useViraState';\nexport type {\n UseViraStateOptions,\n} from './useViraState';\n\n","import { useEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport {\n createViraClient,\n deepMerge,\n getViraConnectionPool,\n type ViraClient,\n type Message,\n type ViraConnectionPool,\n type ViraPoolStatus,\n} from '@vira-ui/core';\n\nexport interface UseViraStateOptions<T = any> {\n /** Initial value for the state */\n initial?: T | null;\n /** Enable msgId support for idempotency */\n enableMsgId?: boolean;\n /** Callback when connection opens */\n onOpen?: () => void;\n /** Callback when connection closes */\n onClose?: (event: CloseEvent) => void;\n /** Callback when connection error occurs */\n onError?: (error: Error) => void;\n /** Use deep merge for diff patches (default: true) */\n deepMerge?: boolean;\n /** API URL (defaults to VITE_API_URL env or 'http://localhost:8080') */\n apiUrl?: string;\n /** Auth token for handshake */\n authToken?: string;\n /** Disable connection pooling (fallback to 1 WS per channel). Default: false */\n disablePooling?: boolean;\n /** Enable debug logs for VRP (console.debug). Default: env VITE_VRP_DEBUG === 'true' */\n debug?: boolean;\n}\n\n/**\n * Unified hook for Vira Reactive Protocol state management.\n * Replaces both useViraState and useViraStream.\n *\n * @example\n * ```tsx\n * // Basic usage\n * const { data, sendUpdate } = useViraState<MyType>('my-channel');\n *\n * // With options\n * const { data, sendUpdate, sendDiff } = useViraState<User>('user:123', {\n * initial: { name: 'Guest' },\n * enableMsgId: true,\n * onOpen: () => console.log('Connected'),\n * deepMerge: true\n * });\n * ```\n */\nexport function useViraState<T = any, C extends string = string>(\n channel: C,\n initialOrOptions?: T | null | UseViraStateOptions<T>\n): {\n /** Current state data */\n data: T | null;\n /** Send an event to the server */\n sendEvent: (name: string, payload: any, msgId?: string) => void;\n /** Send a full update (replaces state) */\n sendUpdate: (payload: T, msgId?: string) => void;\n /** Send a partial diff (merges with current state) */\n sendDiff: (patch: Partial<T>, msgId?: string) => void;\n /** Connection status */\n isConnected: boolean;\n /** Connection error, if any */\n error: Error | null;\n} {\n // Parse options (backward compatibility: second param can be initial value or options)\n const options: UseViraStateOptions<T> = useMemo(() => {\n if (initialOrOptions === null || initialOrOptions === undefined) {\n return {};\n }\n // If it's an object with known option keys, treat as options\n if (\n typeof initialOrOptions === 'object' &&\n !Array.isArray(initialOrOptions) &&\n ('enableMsgId' in initialOrOptions ||\n 'onOpen' in initialOrOptions ||\n 'onClose' in initialOrOptions ||\n 'onError' in initialOrOptions ||\n 'deepMerge' in initialOrOptions ||\n 'initial' in initialOrOptions ||\n 'apiUrl' in initialOrOptions ||\n 'authToken' in initialOrOptions ||\n 'disablePooling' in initialOrOptions ||\n 'debug' in initialOrOptions)\n ) {\n return initialOrOptions as UseViraStateOptions<T>;\n }\n // Otherwise, treat as initial value (backward compatibility)\n return { initial: initialOrOptions as T | null };\n }, [initialOrOptions]);\n\n const {\n initial = null,\n enableMsgId = false,\n onOpen,\n onClose,\n onError,\n deepMerge: useDeepMerge = true,\n apiUrl: apiUrlOption,\n authToken: authTokenOption,\n disablePooling = false,\n debug: debugOption,\n } = options;\n\n const [data, setData] = useState<T | null>(initial);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n type Transport = {\n sendEvent: (name: string, payload: any, msgId?: string) => void;\n sendUpdate: (payload: T, msgId?: string) => void;\n sendDiff: (patch: Partial<T>, msgId?: string) => void;\n };\n\n const transportRef = useRef<Transport | null>(null);\n const clientRef = useRef<ViraClient | null>(null); // legacy non-pooled mode only\n const sessionRef = useRef<string | null>(null);\n const msgIdCounterRef = useRef(0);\n const wasConnectedRef = useRef(false);\n const lastErrorRef = useRef<Error | null>(null);\n\n // Use provided apiUrl or fallback to env or default\n // Note: import.meta is only available in ESM, so we check safely\n const apiUrl = useMemo(() => {\n if (apiUrlOption) return apiUrlOption;\n // Try to get from env if available (Vite/bundler environment)\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n if (env?.VITE_API_URL) return env.VITE_API_URL;\n } catch {\n // Ignore if import.meta is not available\n }\n return 'http://localhost:8080';\n }, [apiUrlOption]);\n\n // Get authToken from options or try to get from env\n const authToken = useMemo(() => {\n if (authTokenOption !== undefined) return authTokenOption;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n return env?.VITE_AUTH_TOKEN || '';\n } catch {\n return '';\n }\n }, [authTokenOption]);\n\n const debug = useMemo(() => {\n if (debugOption !== undefined) return Boolean(debugOption);\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n return String(env?.VITE_VRP_DEBUG || '').toLowerCase() === 'true';\n } catch {\n return false;\n }\n }, [debugOption]);\n\n const pool: ViraConnectionPool | null = useMemo(() => {\n if (disablePooling) return null;\n return getViraConnectionPool({ url: apiUrl, authToken, debug });\n }, [disablePooling, apiUrl, authToken, debug]);\n\n\n\n\n useEffect(() => {\n if (!channel) return;\n\n const handleMessage = (msg: Message) => {\n switch (msg.type) {\n case 'update':\n case 'event':\n if (msg.channel === channel) {\n setData((prev) => {\n if (!prev) {\n return msg.data as T;\n }\n\n if (typeof prev === 'object' && typeof msg.data === 'object') {\n let mergedData;\n if (useDeepMerge) {\n mergedData = deepMerge(prev as Record<string, any>, msg.data as Record<string, any>) as T;\n } else {\n mergedData = { ...(prev as any), ...(msg.data as any) };\n }\n return mergedData;\n }\n\n return msg.data as T;\n });\n }\n break;\n\n case 'diff':\n if (msg.channel === channel && msg.patch) {\n setData((prev) => {\n if (!prev) {\n return msg.patch as T;\n }\n\n if (typeof prev === 'object' && typeof msg.patch === 'object') {\n let mergedData;\n if (useDeepMerge) {\n mergedData = deepMerge(prev as Record<string, any>, msg.patch as Record<string, any>) as T;\n } else {\n mergedData = { ...(prev as any), ...(msg.patch as any) };\n }\n return mergedData;\n }\n\n return prev;\n });\n }\n break;\n }\n };\n\n // --- Pooled mode (default) ---\n if (pool) {\n // Subscribe to messages for this channel\n const unsubChannel = pool.subscribe(channel, handleMessage);\n\n // Track shared connection status\n const unsubStatus = pool.onStatus((status: ViraPoolStatus) => {\n setError(status.error);\n setIsConnected(status.connected);\n\n // Fire callbacks only on transitions\n if (status.connected && !wasConnectedRef.current) {\n wasConnectedRef.current = true;\n onOpen?.();\n }\n if (!status.connected && wasConnectedRef.current) {\n wasConnectedRef.current = false;\n // We don't get a real CloseEvent from pooled status, so we pass a lightweight synthetic object\n const synthetic = (typeof CloseEvent !== 'undefined'\n ? new CloseEvent('close', { code: 1001, reason: 'pooled disconnect' })\n : ({ code: 1001, reason: 'pooled disconnect' } as any)) as any;\n onClose?.(synthetic);\n }\n if (status.error) {\n if (lastErrorRef.current !== status.error) {\n lastErrorRef.current = status.error;\n onError?.(status.error);\n }\n } else {\n lastErrorRef.current = null;\n }\n });\n\n // Wire transport for send* APIs\n transportRef.current = {\n sendEvent: (name, payload, msgId) => pool.sendEvent(channel, name, payload, msgId),\n sendUpdate: (payload, msgId) => pool.sendUpdate(channel, payload, msgId),\n sendDiff: (patch, msgId) => pool.sendDiff(channel, patch, msgId),\n };\n\n return () => {\n unsubStatus();\n unsubChannel();\n transportRef.current = null;\n setIsConnected(false);\n setError(null);\n };\n }\n\n // --- Legacy mode (1 WS per channel) ---\n const handleConnect = () => {\n setIsConnected(true);\n setError(null);\n if (!wasConnectedRef.current) {\n wasConnectedRef.current = true;\n }\n onOpen?.();\n };\n\n const handleDisconnect = (event?: CloseEvent) => {\n setIsConnected(false);\n wasConnectedRef.current = false;\n onClose?.(event!);\n };\n\n const handleError = (err: Error) => {\n setError(err);\n onError?.(err);\n };\n\n const client = createViraClient({\n url: apiUrl,\n channel,\n onMessage: handleMessage,\n onConnect: handleConnect,\n onDisconnect: handleDisconnect,\n onError: handleError,\n session: sessionRef.current,\n authToken,\n onSessionChange: (newSession: string | null) => {\n sessionRef.current = newSession;\n },\n });\n\n clientRef.current = client;\n transportRef.current = {\n sendEvent: (name, payload, msgId) => client.sendEvent(name, payload, msgId),\n sendUpdate: (payload, msgId) => client.sendUpdate(payload, msgId),\n sendDiff: (patch, msgId) => client.sendDiff(patch, msgId),\n };\n\n return () => {\n client.close();\n clientRef.current = null;\n transportRef.current = null;\n setIsConnected(false);\n setError(null);\n };\n }, [channel, apiUrl, authToken, onOpen, onClose, onError, useDeepMerge, pool]);\n\n // Generate msgId if enabled\n const generateMsgId = useCallback((): string | undefined => {\n if (!enableMsgId) return undefined;\n msgIdCounterRef.current++;\n return `${channel}:${Date.now()}:${msgIdCounterRef.current}`;\n }, [channel, enableMsgId]);\n\n const sendEvent = useCallback(\n (name: string, payload: any, msgId?: string) => {\n transportRef.current?.sendEvent(name, payload, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n const sendUpdate = useCallback(\n (payload: T, msgId?: string) => {\n transportRef.current?.sendUpdate(payload, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n const sendDiff = useCallback(\n (patch: Partial<T>, msgId?: string) => {\n transportRef.current?.sendDiff(patch, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n return {\n data,\n sendEvent,\n sendUpdate,\n sendDiff,\n isConnected,\n error,\n };\n}\n\n/**\n * Legacy hook - use useViraState instead.\n * @deprecated Use useViraState with options instead\n */\nexport function useViraStream<T = any, C extends string = string>(\n channel: C,\n options?: UseViraStateOptions<T>\n) {\n return useViraState<T, C>(channel, { ...options, initial: null });\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -67,24 +67,6 @@ function useViraState(channel, initialOrOptions) {
67
67
  if (disablePooling) return null;
68
68
  return getViraConnectionPool({ url: apiUrl, authToken, debug });
69
69
  }, [disablePooling, apiUrl, authToken, debug]);
70
- const loadInitialData = useCallback(async () => {
71
- if (channel === "inventoryitem:") {
72
- const maxRetries = 30;
73
- let retries = 0;
74
- const trySendRequest = () => {
75
- if (transportRef.current && retries < maxRetries) {
76
- transportRef.current.sendEvent("inventoryitem.list", {});
77
- return;
78
- } else if (retries >= maxRetries) {
79
- } else {
80
- retries++;
81
- setTimeout(trySendRequest, 100);
82
- }
83
- };
84
- trySendRequest();
85
- } else {
86
- }
87
- }, [channel]);
88
70
  useEffect(() => {
89
71
  if (!channel) return;
90
72
  const handleMessage = (msg) => {
@@ -92,38 +74,6 @@ function useViraState(channel, initialOrOptions) {
92
74
  case "update":
93
75
  case "event":
94
76
  if (msg.channel === channel) {
95
- if (channel === "inventoryitem:" && msg.data && typeof msg.data === "object" && !msg.data.type && !msg.data.patch && !data) {
96
- setData(msg.data);
97
- return;
98
- }
99
- if (msg.data && typeof msg.data === "object" && msg.data.type === "diff" && msg.data.patch) {
100
- setData((prev) => {
101
- if (!prev) {
102
- return msg.data.patch;
103
- }
104
- if (typeof prev === "object" && typeof msg.data.patch === "object") {
105
- let newData = { ...prev };
106
- for (const [itemId, itemData] of Object.entries(msg.data.patch)) {
107
- if (itemData && typeof itemData === "object" && itemId !== "type" && itemId !== "patch") {
108
- newData[itemId] = itemData;
109
- }
110
- }
111
- return newData;
112
- }
113
- return prev;
114
- });
115
- return;
116
- }
117
- if (msg.data && typeof msg.data === "object" && msg.data.type === "event") {
118
- return;
119
- }
120
- if (msg.data && typeof msg.data === "object" && msg.data.patch) {
121
- return;
122
- }
123
- if (msg.data && typeof msg.data === "object" && msg.data.type === "inventory_list") {
124
- setData(msg.data.items);
125
- return;
126
- }
127
77
  setData((prev) => {
128
78
  if (!prev) {
129
79
  return msg.data;
@@ -142,29 +92,25 @@ function useViraState(channel, initialOrOptions) {
142
92
  }
143
93
  break;
144
94
  case "diff":
145
- if (msg.channel === channel) {
95
+ if (msg.channel === channel && msg.patch) {
146
96
  setData((prev) => {
147
- if (!prev || !msg.patch || typeof msg.patch === "object" && (msg.patch.type === "diff" || msg.patch.type === "event")) {
148
- return prev;
149
- }
150
97
  if (!prev) {
151
- return null;
98
+ return msg.patch;
152
99
  }
153
100
  if (typeof prev === "object" && typeof msg.patch === "object") {
154
- let newData = { ...prev };
155
- for (const [itemId, itemData] of Object.entries(msg.patch)) {
156
- if (itemData && typeof itemData === "object" && itemId !== "type" && itemId !== "patch") {
157
- newData[itemId] = itemData;
158
- }
101
+ let mergedData;
102
+ if (useDeepMerge) {
103
+ mergedData = deepMerge(prev, msg.patch);
104
+ } else {
105
+ mergedData = { ...prev, ...msg.patch };
159
106
  }
160
- return newData;
107
+ return mergedData;
161
108
  }
162
109
  return prev;
163
110
  });
164
111
  }
165
112
  break;
166
113
  }
167
- ;
168
114
  };
169
115
  if (pool) {
170
116
  const unsubChannel = pool.subscribe(channel, handleMessage);
@@ -173,7 +119,6 @@ function useViraState(channel, initialOrOptions) {
173
119
  setIsConnected(status.connected);
174
120
  if (status.connected && !wasConnectedRef.current) {
175
121
  wasConnectedRef.current = true;
176
- loadInitialData();
177
122
  onOpen?.();
178
123
  }
179
124
  if (!status.connected && wasConnectedRef.current) {
@@ -208,7 +153,6 @@ function useViraState(channel, initialOrOptions) {
208
153
  setError(null);
209
154
  if (!wasConnectedRef.current) {
210
155
  wasConnectedRef.current = true;
211
- loadInitialData();
212
156
  }
213
157
  onOpen?.();
214
158
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useViraState.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport {\n createViraClient,\n deepMerge,\n getViraConnectionPool,\n type ViraClient,\n type Message,\n type ViraConnectionPool,\n type ViraPoolStatus,\n} from '@vira-ui/core';\n\nexport interface UseViraStateOptions<T = any> {\n /** Initial value for the state */\n initial?: T | null;\n /** Enable msgId support for idempotency */\n enableMsgId?: boolean;\n /** Callback when connection opens */\n onOpen?: () => void;\n /** Callback when connection closes */\n onClose?: (event: CloseEvent) => void;\n /** Callback when connection error occurs */\n onError?: (error: Error) => void;\n /** Use deep merge for diff patches (default: true) */\n deepMerge?: boolean;\n /** API URL (defaults to VITE_API_URL env or 'http://localhost:8080') */\n apiUrl?: string;\n /** Auth token for handshake */\n authToken?: string;\n /** Disable connection pooling (fallback to 1 WS per channel). Default: false */\n disablePooling?: boolean;\n /** Enable debug logs for VRP (console.debug). Default: env VITE_VRP_DEBUG === 'true' */\n debug?: boolean;\n}\n\n/**\n * Unified hook for Vira Reactive Protocol state management.\n * Replaces both useViraState and useViraStream.\n *\n * @example\n * ```tsx\n * // Basic usage\n * const { data, sendUpdate } = useViraState<MyType>('my-channel');\n *\n * // With options\n * const { data, sendUpdate, sendDiff } = useViraState<User>('user:123', {\n * initial: { name: 'Guest' },\n * enableMsgId: true,\n * onOpen: () => console.log('Connected'),\n * deepMerge: true\n * });\n * ```\n */\nexport function useViraState<T = any, C extends string = string>(\n channel: C,\n initialOrOptions?: T | null | UseViraStateOptions<T>\n): {\n /** Current state data */\n data: T | null;\n /** Send an event to the server */\n sendEvent: (name: string, payload: any, msgId?: string) => void;\n /** Send a full update (replaces state) */\n sendUpdate: (payload: T, msgId?: string) => void;\n /** Send a partial diff (merges with current state) */\n sendDiff: (patch: Partial<T>, msgId?: string) => void;\n /** Connection status */\n isConnected: boolean;\n /** Connection error, if any */\n error: Error | null;\n} {\n // Parse options (backward compatibility: second param can be initial value or options)\n const options: UseViraStateOptions<T> = useMemo(() => {\n if (initialOrOptions === null || initialOrOptions === undefined) {\n return {};\n }\n // If it's an object with known option keys, treat as options\n if (\n typeof initialOrOptions === 'object' &&\n !Array.isArray(initialOrOptions) &&\n ('enableMsgId' in initialOrOptions ||\n 'onOpen' in initialOrOptions ||\n 'onClose' in initialOrOptions ||\n 'onError' in initialOrOptions ||\n 'deepMerge' in initialOrOptions ||\n 'initial' in initialOrOptions ||\n 'apiUrl' in initialOrOptions ||\n 'authToken' in initialOrOptions ||\n 'disablePooling' in initialOrOptions ||\n 'debug' in initialOrOptions)\n ) {\n return initialOrOptions as UseViraStateOptions<T>;\n }\n // Otherwise, treat as initial value (backward compatibility)\n return { initial: initialOrOptions as T | null };\n }, [initialOrOptions]);\n\n const {\n initial = null,\n enableMsgId = false,\n onOpen,\n onClose,\n onError,\n deepMerge: useDeepMerge = true,\n apiUrl: apiUrlOption,\n authToken: authTokenOption,\n disablePooling = false,\n debug: debugOption,\n } = options;\n\n const [data, setData] = useState<T | null>(initial);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n type Transport = {\n sendEvent: (name: string, payload: any, msgId?: string) => void;\n sendUpdate: (payload: T, msgId?: string) => void;\n sendDiff: (patch: Partial<T>, msgId?: string) => void;\n };\n\n const transportRef = useRef<Transport | null>(null);\n const clientRef = useRef<ViraClient | null>(null); // legacy non-pooled mode only\n const sessionRef = useRef<string | null>(null);\n const msgIdCounterRef = useRef(0);\n const wasConnectedRef = useRef(false);\n const lastErrorRef = useRef<Error | null>(null);\n\n // Use provided apiUrl or fallback to env or default\n // Note: import.meta is only available in ESM, so we check safely\n const apiUrl = useMemo(() => {\n if (apiUrlOption) return apiUrlOption;\n // Try to get from env if available (Vite/bundler environment)\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n if (env?.VITE_API_URL) return env.VITE_API_URL;\n } catch {\n // Ignore if import.meta is not available\n }\n return 'http://localhost:8080';\n }, [apiUrlOption]);\n\n // Get authToken from options or try to get from env\n const authToken = useMemo(() => {\n if (authTokenOption !== undefined) return authTokenOption;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n return env?.VITE_AUTH_TOKEN || '';\n } catch {\n return '';\n }\n }, [authTokenOption]);\n\n const debug = useMemo(() => {\n if (debugOption !== undefined) return Boolean(debugOption);\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n return String(env?.VITE_VRP_DEBUG || '').toLowerCase() === 'true';\n } catch {\n return false;\n }\n }, [debugOption]);\n\n const pool: ViraConnectionPool | null = useMemo(() => {\n if (disablePooling) return null;\n return getViraConnectionPool({ url: apiUrl, authToken, debug });\n }, [disablePooling, apiUrl, authToken, debug]);\n\n // ДОБАВИТЬ ЭТУ ФУНКЦИЮ:\n const loadInitialData = useCallback(async () => {\n\n\n // Если это канал товаров, ждем транспорт и запрашиваем данные\n if (channel === 'inventoryitem:') {\n\n // Ждем до 3 секунд пока транспорт будет готов\n const maxRetries = 30;\n let retries = 0;\n\n const trySendRequest = () => {\n if (transportRef.current && retries < maxRetries) {\n transportRef.current.sendEvent('inventoryitem.list', {});\n return;\n } else if (retries >= maxRetries) {\n } else {\n retries++;\n setTimeout(trySendRequest, 100);\n }\n };\n\n trySendRequest();\n } else {\n }\n }, [channel]);\n\n\n\n\n useEffect(() => {\n if (!channel) return;\n\n const handleMessage = (msg: Message) => {\n\n switch (msg.type) {\n case 'update':\n case 'event':\n if (msg.channel === channel) {\n // НОВАЯ ЛОГИКА: Если это первое update с полными данными товаров\n if (channel === 'inventoryitem:' && msg.data && typeof msg.data === 'object' &&\n !msg.data.type && !msg.data.patch && !data) {\n setData(msg.data as T);\n return;\n }\n\n // Handle diff patches that come as update messages\n if (msg.data && typeof msg.data === 'object' && msg.data.type === 'diff' && msg.data.patch) {\n\n setData((prev) => {\n\n if (!prev) {\n return msg.data.patch as T;\n }\n\n // Merge diff данных с существующими данными\n if (typeof prev === 'object' && typeof msg.data.patch === 'object') {\n let newData = { ...(prev as any) };\n\n // Обновляем только элементы из diff patch\n for (const [itemId, itemData] of Object.entries(msg.data.patch)) {\n if (itemData && typeof itemData === 'object' && itemId !== 'type' && itemId !== 'patch') {\n newData[itemId] = itemData;\n }\n }\n\n return newData as T;\n }\n\n return prev;\n });\n return;\n }\n\n // Handle events that come as update messages (ignore service events)\n if (msg.data && typeof msg.data === 'object' && msg.data.type === 'event') {\n return;\n }\n\n // ПРОВЕРКА: Игнорируем другие служебные сообщения\n if (msg.data && typeof msg.data === 'object' && msg.data.patch) {\n return; // Выходим без обновления данных\n }\n\n // Обработка запроса начальных данных\n if (msg.data && typeof msg.data === 'object' && msg.data.type === 'inventory_list') {\n setData(msg.data.items as T);\n return;\n }\n\n\n setData((prev) => {\n\n if (!prev) {\n return msg.data as T;\n }\n\n if (typeof prev === 'object' && typeof msg.data === 'object') {\n let mergedData;\n if (useDeepMerge) {\n mergedData = deepMerge(prev as Record<string, any>, msg.data as Record<string, any>) as T;\n } else {\n mergedData = { ...(prev as any), ...(msg.data as any) };\n }\n return mergedData;\n }\n\n return msg.data as T;\n });\n }\n break;\n\n\n case 'diff':\n if (msg.channel === channel) {\n // СПЕЦИАЛЬНАЯ ЛОГИКА для diff - только для товаров\n setData((prev) => {\n\n // Игнорируем служебные diff (с type: 'diff')\n if (!prev || !msg.patch ||\n (typeof msg.patch === 'object' && (msg.patch.type === 'diff' || msg.patch.type === 'event'))) {\n return prev;\n }\n\n if (!prev) {\n return null; // Возвращаем null для соответствия типу T | null\n }\n\n // Merge diff данных с существующими товарами\n if (typeof prev === 'object' && typeof msg.patch === 'object') {\n let newData = { ...(prev as any) };\n\n // Обновляем только товары из diff\n for (const [itemId, itemData] of Object.entries(msg.patch)) {\n if (itemData && typeof itemData === 'object' && itemId !== 'type' && itemId !== 'patch') {\n newData[itemId] = itemData;\n }\n }\n\n return newData as T;\n }\n\n return prev;\n });\n }\n break;\n };\n };\n\n // --- Pooled mode (default) ---\n if (pool) {\n // Subscribe to messages for this channel\n const unsubChannel = pool.subscribe(channel, handleMessage);\n\n // Track shared connection status\n const unsubStatus = pool.onStatus((status: ViraPoolStatus) => {\n setError(status.error);\n setIsConnected(status.connected);\n\n // Fire callbacks only on transitions\n if (status.connected && !wasConnectedRef.current) {\n wasConnectedRef.current = true;\n loadInitialData();\n onOpen?.();\n }\n if (!status.connected && wasConnectedRef.current) {\n wasConnectedRef.current = false;\n // We don't get a real CloseEvent from pooled status, so we pass a lightweight synthetic object\n const synthetic = (typeof CloseEvent !== 'undefined'\n ? new CloseEvent('close', { code: 1001, reason: 'pooled disconnect' })\n : ({ code: 1001, reason: 'pooled disconnect' } as any)) as any;\n onClose?.(synthetic);\n }\n if (status.error) {\n if (lastErrorRef.current !== status.error) {\n lastErrorRef.current = status.error;\n onError?.(status.error);\n }\n } else {\n lastErrorRef.current = null;\n }\n });\n\n // Wire transport for send* APIs\n transportRef.current = {\n sendEvent: (name, payload, msgId) => pool.sendEvent(channel, name, payload, msgId),\n sendUpdate: (payload, msgId) => pool.sendUpdate(channel, payload, msgId),\n sendDiff: (patch, msgId) => pool.sendDiff(channel, patch, msgId),\n };\n\n return () => {\n unsubStatus();\n unsubChannel();\n transportRef.current = null;\n setIsConnected(false);\n setError(null);\n };\n }\n\n // --- Legacy mode (1 WS per channel) ---\n const handleConnect = () => {\n setIsConnected(true);\n setError(null);\n if (!wasConnectedRef.current) {\n wasConnectedRef.current = true;\n loadInitialData();\n }\n onOpen?.();\n };\n\n const handleDisconnect = (event?: CloseEvent) => {\n setIsConnected(false);\n wasConnectedRef.current = false;\n onClose?.(event!);\n };\n\n const handleError = (err: Error) => {\n setError(err);\n onError?.(err);\n };\n\n const client = createViraClient({\n url: apiUrl,\n channel,\n onMessage: handleMessage,\n onConnect: handleConnect,\n onDisconnect: handleDisconnect,\n onError: handleError,\n session: sessionRef.current,\n authToken,\n onSessionChange: (newSession: string | null) => {\n sessionRef.current = newSession;\n },\n });\n\n clientRef.current = client;\n transportRef.current = {\n sendEvent: (name, payload, msgId) => client.sendEvent(name, payload, msgId),\n sendUpdate: (payload, msgId) => client.sendUpdate(payload, msgId),\n sendDiff: (patch, msgId) => client.sendDiff(patch, msgId),\n };\n\n return () => {\n client.close();\n clientRef.current = null;\n transportRef.current = null;\n setIsConnected(false);\n setError(null);\n };\n }, [channel, apiUrl, authToken, onOpen, onClose, onError, useDeepMerge, pool]);\n\n // Generate msgId if enabled\n const generateMsgId = useCallback((): string | undefined => {\n if (!enableMsgId) return undefined;\n msgIdCounterRef.current++;\n return `${channel}:${Date.now()}:${msgIdCounterRef.current}`;\n }, [channel, enableMsgId]);\n\n const sendEvent = useCallback(\n (name: string, payload: any, msgId?: string) => {\n transportRef.current?.sendEvent(name, payload, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n const sendUpdate = useCallback(\n (payload: T, msgId?: string) => {\n transportRef.current?.sendUpdate(payload, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n const sendDiff = useCallback(\n (patch: Partial<T>, msgId?: string) => {\n transportRef.current?.sendDiff(patch, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n return {\n data,\n sendEvent,\n sendUpdate,\n sendDiff,\n isConnected,\n error,\n };\n}\n\n/**\n * Legacy hook - use useViraState instead.\n * @deprecated Use useViraState with options instead\n */\nexport function useViraStream<T = any, C extends string = string>(\n channel: C,\n options?: UseViraStateOptions<T>\n) {\n return useViraState<T, C>(channel, { ...options, initial: null });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/useViraState.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState, useCallback } from 'react';\nimport {\n createViraClient,\n deepMerge,\n getViraConnectionPool,\n type ViraClient,\n type Message,\n type ViraConnectionPool,\n type ViraPoolStatus,\n} from '@vira-ui/core';\n\nexport interface UseViraStateOptions<T = any> {\n /** Initial value for the state */\n initial?: T | null;\n /** Enable msgId support for idempotency */\n enableMsgId?: boolean;\n /** Callback when connection opens */\n onOpen?: () => void;\n /** Callback when connection closes */\n onClose?: (event: CloseEvent) => void;\n /** Callback when connection error occurs */\n onError?: (error: Error) => void;\n /** Use deep merge for diff patches (default: true) */\n deepMerge?: boolean;\n /** API URL (defaults to VITE_API_URL env or 'http://localhost:8080') */\n apiUrl?: string;\n /** Auth token for handshake */\n authToken?: string;\n /** Disable connection pooling (fallback to 1 WS per channel). Default: false */\n disablePooling?: boolean;\n /** Enable debug logs for VRP (console.debug). Default: env VITE_VRP_DEBUG === 'true' */\n debug?: boolean;\n}\n\n/**\n * Unified hook for Vira Reactive Protocol state management.\n * Replaces both useViraState and useViraStream.\n *\n * @example\n * ```tsx\n * // Basic usage\n * const { data, sendUpdate } = useViraState<MyType>('my-channel');\n *\n * // With options\n * const { data, sendUpdate, sendDiff } = useViraState<User>('user:123', {\n * initial: { name: 'Guest' },\n * enableMsgId: true,\n * onOpen: () => console.log('Connected'),\n * deepMerge: true\n * });\n * ```\n */\nexport function useViraState<T = any, C extends string = string>(\n channel: C,\n initialOrOptions?: T | null | UseViraStateOptions<T>\n): {\n /** Current state data */\n data: T | null;\n /** Send an event to the server */\n sendEvent: (name: string, payload: any, msgId?: string) => void;\n /** Send a full update (replaces state) */\n sendUpdate: (payload: T, msgId?: string) => void;\n /** Send a partial diff (merges with current state) */\n sendDiff: (patch: Partial<T>, msgId?: string) => void;\n /** Connection status */\n isConnected: boolean;\n /** Connection error, if any */\n error: Error | null;\n} {\n // Parse options (backward compatibility: second param can be initial value or options)\n const options: UseViraStateOptions<T> = useMemo(() => {\n if (initialOrOptions === null || initialOrOptions === undefined) {\n return {};\n }\n // If it's an object with known option keys, treat as options\n if (\n typeof initialOrOptions === 'object' &&\n !Array.isArray(initialOrOptions) &&\n ('enableMsgId' in initialOrOptions ||\n 'onOpen' in initialOrOptions ||\n 'onClose' in initialOrOptions ||\n 'onError' in initialOrOptions ||\n 'deepMerge' in initialOrOptions ||\n 'initial' in initialOrOptions ||\n 'apiUrl' in initialOrOptions ||\n 'authToken' in initialOrOptions ||\n 'disablePooling' in initialOrOptions ||\n 'debug' in initialOrOptions)\n ) {\n return initialOrOptions as UseViraStateOptions<T>;\n }\n // Otherwise, treat as initial value (backward compatibility)\n return { initial: initialOrOptions as T | null };\n }, [initialOrOptions]);\n\n const {\n initial = null,\n enableMsgId = false,\n onOpen,\n onClose,\n onError,\n deepMerge: useDeepMerge = true,\n apiUrl: apiUrlOption,\n authToken: authTokenOption,\n disablePooling = false,\n debug: debugOption,\n } = options;\n\n const [data, setData] = useState<T | null>(initial);\n const [isConnected, setIsConnected] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n type Transport = {\n sendEvent: (name: string, payload: any, msgId?: string) => void;\n sendUpdate: (payload: T, msgId?: string) => void;\n sendDiff: (patch: Partial<T>, msgId?: string) => void;\n };\n\n const transportRef = useRef<Transport | null>(null);\n const clientRef = useRef<ViraClient | null>(null); // legacy non-pooled mode only\n const sessionRef = useRef<string | null>(null);\n const msgIdCounterRef = useRef(0);\n const wasConnectedRef = useRef(false);\n const lastErrorRef = useRef<Error | null>(null);\n\n // Use provided apiUrl or fallback to env or default\n // Note: import.meta is only available in ESM, so we check safely\n const apiUrl = useMemo(() => {\n if (apiUrlOption) return apiUrlOption;\n // Try to get from env if available (Vite/bundler environment)\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n if (env?.VITE_API_URL) return env.VITE_API_URL;\n } catch {\n // Ignore if import.meta is not available\n }\n return 'http://localhost:8080';\n }, [apiUrlOption]);\n\n // Get authToken from options or try to get from env\n const authToken = useMemo(() => {\n if (authTokenOption !== undefined) return authTokenOption;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n return env?.VITE_AUTH_TOKEN || '';\n } catch {\n return '';\n }\n }, [authTokenOption]);\n\n const debug = useMemo(() => {\n if (debugOption !== undefined) return Boolean(debugOption);\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const env = (globalThis as any).import?.meta?.env || (globalThis as any).process?.env;\n return String(env?.VITE_VRP_DEBUG || '').toLowerCase() === 'true';\n } catch {\n return false;\n }\n }, [debugOption]);\n\n const pool: ViraConnectionPool | null = useMemo(() => {\n if (disablePooling) return null;\n return getViraConnectionPool({ url: apiUrl, authToken, debug });\n }, [disablePooling, apiUrl, authToken, debug]);\n\n\n\n\n useEffect(() => {\n if (!channel) return;\n\n const handleMessage = (msg: Message) => {\n switch (msg.type) {\n case 'update':\n case 'event':\n if (msg.channel === channel) {\n setData((prev) => {\n if (!prev) {\n return msg.data as T;\n }\n\n if (typeof prev === 'object' && typeof msg.data === 'object') {\n let mergedData;\n if (useDeepMerge) {\n mergedData = deepMerge(prev as Record<string, any>, msg.data as Record<string, any>) as T;\n } else {\n mergedData = { ...(prev as any), ...(msg.data as any) };\n }\n return mergedData;\n }\n\n return msg.data as T;\n });\n }\n break;\n\n case 'diff':\n if (msg.channel === channel && msg.patch) {\n setData((prev) => {\n if (!prev) {\n return msg.patch as T;\n }\n\n if (typeof prev === 'object' && typeof msg.patch === 'object') {\n let mergedData;\n if (useDeepMerge) {\n mergedData = deepMerge(prev as Record<string, any>, msg.patch as Record<string, any>) as T;\n } else {\n mergedData = { ...(prev as any), ...(msg.patch as any) };\n }\n return mergedData;\n }\n\n return prev;\n });\n }\n break;\n }\n };\n\n // --- Pooled mode (default) ---\n if (pool) {\n // Subscribe to messages for this channel\n const unsubChannel = pool.subscribe(channel, handleMessage);\n\n // Track shared connection status\n const unsubStatus = pool.onStatus((status: ViraPoolStatus) => {\n setError(status.error);\n setIsConnected(status.connected);\n\n // Fire callbacks only on transitions\n if (status.connected && !wasConnectedRef.current) {\n wasConnectedRef.current = true;\n onOpen?.();\n }\n if (!status.connected && wasConnectedRef.current) {\n wasConnectedRef.current = false;\n // We don't get a real CloseEvent from pooled status, so we pass a lightweight synthetic object\n const synthetic = (typeof CloseEvent !== 'undefined'\n ? new CloseEvent('close', { code: 1001, reason: 'pooled disconnect' })\n : ({ code: 1001, reason: 'pooled disconnect' } as any)) as any;\n onClose?.(synthetic);\n }\n if (status.error) {\n if (lastErrorRef.current !== status.error) {\n lastErrorRef.current = status.error;\n onError?.(status.error);\n }\n } else {\n lastErrorRef.current = null;\n }\n });\n\n // Wire transport for send* APIs\n transportRef.current = {\n sendEvent: (name, payload, msgId) => pool.sendEvent(channel, name, payload, msgId),\n sendUpdate: (payload, msgId) => pool.sendUpdate(channel, payload, msgId),\n sendDiff: (patch, msgId) => pool.sendDiff(channel, patch, msgId),\n };\n\n return () => {\n unsubStatus();\n unsubChannel();\n transportRef.current = null;\n setIsConnected(false);\n setError(null);\n };\n }\n\n // --- Legacy mode (1 WS per channel) ---\n const handleConnect = () => {\n setIsConnected(true);\n setError(null);\n if (!wasConnectedRef.current) {\n wasConnectedRef.current = true;\n }\n onOpen?.();\n };\n\n const handleDisconnect = (event?: CloseEvent) => {\n setIsConnected(false);\n wasConnectedRef.current = false;\n onClose?.(event!);\n };\n\n const handleError = (err: Error) => {\n setError(err);\n onError?.(err);\n };\n\n const client = createViraClient({\n url: apiUrl,\n channel,\n onMessage: handleMessage,\n onConnect: handleConnect,\n onDisconnect: handleDisconnect,\n onError: handleError,\n session: sessionRef.current,\n authToken,\n onSessionChange: (newSession: string | null) => {\n sessionRef.current = newSession;\n },\n });\n\n clientRef.current = client;\n transportRef.current = {\n sendEvent: (name, payload, msgId) => client.sendEvent(name, payload, msgId),\n sendUpdate: (payload, msgId) => client.sendUpdate(payload, msgId),\n sendDiff: (patch, msgId) => client.sendDiff(patch, msgId),\n };\n\n return () => {\n client.close();\n clientRef.current = null;\n transportRef.current = null;\n setIsConnected(false);\n setError(null);\n };\n }, [channel, apiUrl, authToken, onOpen, onClose, onError, useDeepMerge, pool]);\n\n // Generate msgId if enabled\n const generateMsgId = useCallback((): string | undefined => {\n if (!enableMsgId) return undefined;\n msgIdCounterRef.current++;\n return `${channel}:${Date.now()}:${msgIdCounterRef.current}`;\n }, [channel, enableMsgId]);\n\n const sendEvent = useCallback(\n (name: string, payload: any, msgId?: string) => {\n transportRef.current?.sendEvent(name, payload, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n const sendUpdate = useCallback(\n (payload: T, msgId?: string) => {\n transportRef.current?.sendUpdate(payload, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n const sendDiff = useCallback(\n (patch: Partial<T>, msgId?: string) => {\n transportRef.current?.sendDiff(patch, msgId ?? generateMsgId());\n },\n [generateMsgId]\n );\n\n return {\n data,\n sendEvent,\n sendUpdate,\n sendDiff,\n isConnected,\n error,\n };\n}\n\n/**\n * Legacy hook - use useViraState instead.\n * @deprecated Use useViraState with options instead\n */\nexport function useViraStream<T = any, C extends string = string>(\n channel: C,\n options?: UseViraStateOptions<T>\n) {\n return useViraState<T, C>(channel, { ...options, initial: null });\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vira-ui/react",
3
- "version": "1.1.1",
3
+ "version": "2.0.1",
4
4
  "description": "Vira Framework - React hooks for Vira Reactive Protocol",
5
5
  "author": "Vira Team",
6
6
  "license": "MIT",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "react": "^18.2.0",
31
- "@vira-ui/core": "1.1.1"
31
+ "@vira-ui/core": "3.0.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "react": "^18.2.0"