UNPKG

@yunu-lab/rpc-react

Version:
534 lines (533 loc) 16.9 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const toolkit = require("@reduxjs/toolkit"); const jsxRuntime = require("react/jsx-runtime"); const React = require("react"); const reactRedux = require("react-redux"); const rpcTs = require("@yunu-lab/rpc-ts"); const extendStore = (options) => { const { repository, store, slices = {} } = options; const rpcSlice = toolkit.createSlice({ name: "rpc", initialState: {}, reducers: { setData: (state, action) => { const { type, data } = action.payload; state[type] = data; } } }); const newReducer = toolkit.combineReducers({ ...slices, rpc: rpcSlice.reducer }); store.replaceReducer(newReducer); const unsubscribe = repository.onDataChanged((events) => { events.forEach((event) => { const { type, payload } = event; const storageType = repository.getStorageType(String(type)); if (storageType === "singleton") { const nextValue = Array.isArray(payload) ? payload[0] ?? null : payload ?? null; store.dispatch( rpcSlice.actions.setData({ type: String(type), data: nextValue }) ); } else { const nextArray = Array.isArray(payload) ? payload : Object.values(payload || {}); store.dispatch( rpcSlice.actions.setData({ type: String(type), data: nextArray }) ); } }); }); return { store, repository, unsubscribe }; }; const RpcContext = React.createContext(null); const RpcProvider = ({ children, repository }) => { return /* @__PURE__ */ jsxRuntime.jsx(RpcContext.Provider, { value: { repository }, children }); }; const useRpc = () => { const context = React.useContext(RpcContext); if (!context) { throw new Error("useRpc must be used within an RpcProvider"); } const repository = React.useMemo( () => context.repository, [context.repository] ); return { repository }; }; const toPascalCase = (s) => { return s.split("_").map( (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() ).join(""); }; const createRpcHooks = (typeKeys) => { const EMPTY_MAP = Object.freeze({}); const hooks = {}; typeKeys.forEach((typeName) => { const hookName = `use${toPascalCase( String(typeName) )}`; const typeKey = typeName; function useHook(id) { const { repository } = useRpc(); const allData = reactRedux.useSelector( (state) => state.rpc[typeKey] ?? EMPTY_MAP ); const storageType = repository.getStorageType?.( String(typeKey) ); const list = React.useMemo(() => { if (storageType === "singleton") return []; if (Array.isArray(allData)) { return allData; } return Object.values(allData || {}); }, [allData, storageType]); const findById = React.useCallback( (id2) => repository.findById(typeKey, id2), [repository] ); const findAll = React.useCallback( () => repository.findAll(typeKey), [repository] ); const mergeRpc = React.useCallback( (data) => { repository.mergeRpc(typeKey, data); }, [repository] ); const clear = React.useCallback(() => { repository.clear(typeKey); }, [repository]); const dataForKey = storageType === "singleton" ? allData ?? null : list; const mapForKey = React.useMemo(() => { if (storageType === "singleton") { return allData ?? null; } const record = {}; for (const item of list) { if (item && "id" in item) { record[String(item.id)] = item; } } return record; }, [allData, list, storageType]); if (id !== void 0) { return findById(id); } return { [`${String(typeKey)}s`]: dataForKey, [`${String(typeKey)}Map`]: mapForKey, findById, findAll, mergeRpc, clear }; } hooks[hookName] = useHook; const fullRelatedHookName = `use${toPascalCase( String(typeName) )}FullRelatedData`; function useFullRelatedHook(id) { const { repository } = useRpc(); const allRpcData = reactRedux.useSelector( (state) => state.rpc ); const rpcVersion = React.useMemo(() => { return Object.keys(allRpcData).length; }, [allRpcData]); const fullData = React.useMemo(() => { try { return repository.getFullRelatedData( typeName, id ); } catch { return null; } }, [repository, id, rpcVersion]); return fullData; } hooks[fullRelatedHookName] = useFullRelatedHook; }); typeKeys.forEach((typeName) => { const listenerHookName = `use${toPascalCase( String(typeName) )}Listener`; function useListenerHook(callback) { const { repository } = useRpc(); const callbackRef = React.useRef(callback); const listenerIdRef = React.useRef(null); callbackRef.current = callback; const storageType = React.useMemo(() => { return repository.getStorageType?.(String(typeName)); }, [repository]); const invoke = React.useCallback( (events) => { if (!events || events.length === 0) return; const event = events[0]; if (storageType === "singleton" && Array.isArray(event.payload) && event.payload.length > 0) { const singletonEvent = { ...event, payload: event.payload[0] }; callbackRef.current(singletonEvent); } else { callbackRef.current(event); } }, [storageType] ); React.useEffect(() => { if (listenerIdRef.current && typeof repository.offDataChanged === "function") { repository.offDataChanged(listenerIdRef.current); listenerIdRef.current = null; } listenerIdRef.current = repository.onDataChanged( invoke, { types: [typeName] } ); return () => { if (listenerIdRef.current && typeof repository.offDataChanged === "function") { repository.offDataChanged( listenerIdRef.current ); listenerIdRef.current = null; } }; }, [repository, invoke]); return () => { if (listenerIdRef.current && typeof repository.offDataChanged === "function") { repository.offDataChanged(listenerIdRef.current); listenerIdRef.current = null; } }; } hooks[listenerHookName] = useListenerHook; }); function useDataListener(callback, options) { const { repository } = useRpc(); const callbackRef = React.useRef(callback); const listenerIdRef = React.useRef(null); callbackRef.current = callback; const types = options?.types || typeKeys; const typesKey = React.useMemo(() => { const sorted = [...types].sort(); return sorted.join(","); }, [types]); const stableTypes = React.useMemo(() => { return options?.types || typeKeys; }, [typesKey, options?.types]); React.useEffect(() => { if (listenerIdRef.current && typeof repository.offDataChanged === "function") { repository.offDataChanged(listenerIdRef.current); listenerIdRef.current = null; } listenerIdRef.current = repository.onDataChanged( callbackRef.current, { types: stableTypes } ); return () => { if (listenerIdRef.current && typeof repository.offDataChanged === "function") { repository.offDataChanged(listenerIdRef.current); listenerIdRef.current = null; } }; }, [repository, stableTypes]); return () => { if (listenerIdRef.current && typeof repository.offDataChanged === "function") { repository.offDataChanged(listenerIdRef.current); listenerIdRef.current = null; } }; } hooks.useDataListener = useDataListener; typeKeys.forEach((typeName) => { const relatedHookName = `use${toPascalCase( String(typeName) )}Related`; function useRelatedHook(id, targetType) { const { repository } = useRpc(); const sourceData = reactRedux.useSelector( (state) => state.rpc[typeName] ?? EMPTY_MAP ); const targetData = reactRedux.useSelector( (state) => state.rpc[targetType] ?? EMPTY_MAP ); const dataVersion = React.useMemo(() => { const sourceKeys = Object.keys(sourceData).length; const targetKeys = Object.keys(targetData).length; return `${sourceKeys}:${targetKeys}`; }, [sourceData, targetData]); const relatedData = React.useMemo(() => { try { return repository.getRelated( typeName, id, targetType ); } catch { return []; } }, [repository, id, targetType, dataVersion]); return relatedData; } hooks[relatedHookName] = useRelatedHook; }); function useHandleMessages() { const { repository } = useRpc(); const handleMessages = (messages, callbacks) => { repository.handleMessages(messages, callbacks); }; const handleMessagesTyped = (messages, callbacks) => { repository.handleMessages(messages, callbacks); }; return { handleMessages, handleMessagesTyped }; } hooks.useHandleMessages = useHandleMessages; return hooks; }; function useInlineRpcStore(rpcs) { const typeNames = React.useMemo( () => Object.keys(rpcs), [rpcs] ); const typeNamesKey = React.useMemo( () => Object.keys(rpcs).map(String).sort().join("|"), [rpcs] ); const repository = React.useMemo(() => { const repo = new rpcTs.RpcRepository(); typeNames.forEach((key) => { const { rpc, storageType } = rpcs[key]; repo.registerRpc(String(key), rpc, { storageType }); }); return repo; }, [typeNamesKey]); const [rpcState, setRpcState] = React.useState({}); React.useEffect(() => { const unsubscribe = repository.onDataChanged( (events) => { console.log(events); setRpcState((prev) => { const next = { ...prev }; for (const event of events) { const type = event.type; const storageType = rpcs[type]?.storageType ?? repository.getStorageType?.(String(type)); if (storageType === "singleton") { next[type] = Array.isArray(event.payload) ? event.payload[0] ?? null : event.payload ?? null; } else { const nextArray = Array.isArray(event.payload) ? event.payload : Object.values(event.payload || {}); next[type] = nextArray; } } return next; }); }, { types: typeNames } ); return () => { repository.offDataChanged(unsubscribe); }; }, [repository, typeNamesKey]); const useInlineSelector = React.useCallback( (selector) => { return selector(rpcState); }, [rpcState] ); const hooks = React.useMemo(() => { const toPascalCase2 = (s) => s.split("_").map( (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() ).join(""); const result = {}; Object.keys(rpcs).forEach((typeName) => { const storageType = rpcs[typeName].storageType; const hookName = `use${toPascalCase2(String(typeName))}`; function useTypeHook(id) { const allData = useInlineSelector((state) => state[typeName]); const findById = React.useCallback( (itemId) => repository.findById(typeName, itemId), [] ); const findAll = React.useCallback( () => repository.findAll(typeName), [] ); const mergeRpc = React.useCallback( (data) => { repository.mergeRpc(typeName, data); }, [] ); const clear = React.useCallback(() => { if (storageType === "singleton") { setRpcState((prev) => ({ ...prev, [typeName]: null })); } else { const ids = Array.isArray( allData ) ? allData.map( (item) => item ? item.id : void 0 ).filter((v) => v !== void 0) : Object.keys(allData || {}); const deletePayload = {}; for (const id2 of ids) deletePayload[id2] = null; if (Object.keys(deletePayload).length > 0) { repository.mergeRpc(typeName, deletePayload); } setRpcState((prev) => ({ ...prev, [typeName]: [] })); } }, [allData]); const list = React.useMemo( () => Array.isArray(allData) ? allData : Object.values(allData || {}), [allData] ); const collectionMap = React.useMemo(() => { const rec = {}; if (Array.isArray(list)) { for (const item of list) { if (item && typeof item === "object" && !Array.isArray(item) && "id" in item) { rec[item.id] = item; } } } return rec; }, [list]); if (id !== void 0) { return findById(id); } if (storageType === "singleton") { const current = allData || null; return { [`${String(typeName)}s`]: current, [`${String(typeName)}Map`]: current, findById, findAll, mergeRpc, clear }; } return { [`${String(typeName)}s`]: list, [`${String(typeName)}Map`]: collectionMap, findById, findAll, mergeRpc, clear }; } result[hookName] = useTypeHook; const listenerHookName = `use${toPascalCase2( String(typeName) )}Listener`; function useListenerHook(callback) { const callbackRef = React.useRef(callback); callbackRef.current = callback; React.useEffect(() => { const unsub = repository.onDataChanged( (events) => { const filtered = events.filter( (e) => e.type === typeName ); if (filtered.length === 0) return; const event = filtered[0]; const storageType2 = repository.getStorageType( String(typeName) ); if (storageType2 === "singleton" && Array.isArray(event.payload)) { const payload = event.payload[0] ?? null; if (payload) { callbackRef.current({ type: typeName, payload }); } } else { callbackRef.current(event); } }, { types: [typeName] } ); return () => { repository.offDataChanged(unsub); }; }, []); return () => { }; } result[listenerHookName] = useListenerHook; }); function useDataListener(callback, options) { const callbackRef = React.useRef(callback); callbackRef.current = callback; const types = options?.types ?? typeNames; const typesKey = React.useMemo( () => [...types].map(String).sort().join("|"), [types] ); React.useEffect(() => { const unsub = repository.onDataChanged( (events) => callbackRef.current(events), { types } ); return () => { repository.offDataChanged(unsub); }; }, [typesKey, types]); return () => { }; } result.useDataListener = useDataListener; function useHandleMessages() { const handleMessages = (messages, callbacks) => { repository.handleMessages(messages, callbacks); }; const handleMessagesTyped = (messages, callbacks) => { repository.handleMessages( messages, callbacks ); }; return { handleMessages, handleMessagesTyped }; } result.useHandleMessages = useHandleMessages; return result; }, [rpcs, repository, useInlineSelector, typeNames]); return { hooks, repository }; } Object.defineProperty(exports, "Rpc", { enumerable: true, get: () => rpcTs.Rpc }); Object.defineProperty(exports, "RpcRepository", { enumerable: true, get: () => rpcTs.RpcRepository }); exports.RpcProvider = RpcProvider; exports.createRpcHooks = createRpcHooks; exports.extendStore = extendStore; exports.useInlineRpcStore = useInlineRpcStore; exports.useRpc = useRpc; //# sourceMappingURL=index.js.map