@medplum/react-hooks 3.2.10 → 3.2.12
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/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/cjs/index.d.ts +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +2 -2
- package/package.json +9 -9
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var I=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var q=(e,
|
|
1
|
+
"use strict";var I=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var q=(e,n)=>{for(var t in n)I(e,t,{get:n[t],enumerable:!0})},G=(e,n,t,s)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of J(n))!X.call(e,r)&&r!==t&&I(e,r,{get:()=>n[r],enumerable:!(s=V(n,r))||s.enumerable});return e};var H=e=>G(I({},"__esModule",{value:!0}),e);var ce={};q(ce,{MedplumProvider:()=>$,reactContext:()=>C,useCachedBinaryUrl:()=>ee,useMedplum:()=>m,useMedplumContext:()=>h,useMedplumNavigate:()=>Y,useMedplumProfile:()=>Z,usePrevious:()=>ne,useResource:()=>te,useSearch:()=>re,useSearchOne:()=>oe,useSearchResources:()=>ie,useSubscription:()=>ue});module.exports=H(ce);var v=require("react");var g=require("react"),C=(0,g.createContext)(void 0);function h(){return(0,g.useContext)(C)}function m(){return h().medplum}function Y(){return h().navigate}function Z(){return h().profile}var z=require("react/jsx-runtime"),D=["change","storageInitialized","storageInitFailed","profileRefreshing","profileRefreshed"];function $(e){let n=e.medplum,t=e.navigate??j,[s,r]=(0,v.useState)({profile:n.getProfile(),loading:n.isLoading()});(0,v.useEffect)(()=>{function i(){r(c=>({...c,profile:n.getProfile(),loading:n.isLoading()}))}for(let c of D)n.addEventListener(c,i);return()=>{for(let c of D)n.removeEventListener(c,i)}},[n]);let u=(0,v.useMemo)(()=>({...s,medplum:n,navigate:t}),[s,n,t]);return(0,z.jsx)(C.Provider,{value:u,children:e.children})}function j(e){window.location.assign(e)}var Q=require("react"),_=new Map,ee=e=>(0,Q.useMemo)(()=>{if(!e)return;let n=e.split("?")[0];if(!n)return e;let t;try{t=new URLSearchParams(new URL(e).search)}catch{return e}if(!t.has("Key-Pair-Id")||!t.has("Signature"))return e;let s=t.get("Expires");if(!s||s.length>13)return e;let r=_.get(n);if(r){let i=new URLSearchParams(new URL(r).search).get("Expires");if(i&&parseInt(i,10)*1e3-5e3>Date.now())return r}return _.set(n,e),e},[e]);var M=require("react");function ne(e){let n=(0,M.useRef)();return(0,M.useEffect)(()=>{n.current=e}),n.current}var a=require("@medplum/core"),E=require("react");function te(e,n){let t=m(),[s,r]=(0,E.useState)(()=>A(t,e)),u=(0,E.useCallback)(i=>{(0,a.deepEquals)(i,s)||r(i)},[s]);return(0,E.useEffect)(()=>{let i=!0,c=A(t,e);return!c&&(0,a.isReference)(e)?t.readReference(e).then(d=>{i&&u(d)}).catch(d=>{i&&(u(void 0),n&&n((0,a.normalizeOperationOutcome)(d)))}):u(c),()=>i=!1},[t,e,u,n]),s}function A(e,n){if(n){if((0,a.isResource)(n))return n;if((0,a.isReference)(n))return e.getCachedReference(n)}}var O=require("@medplum/core"),l=require("react");function re(e,n){return N("search",e,n)}function oe(e,n){return N("searchOne",e,n)}function ie(e,n){return N("searchResources",e,n)}function N(e,n,t){let s=m(),[r,u]=(0,l.useState)(),[i,c]=(0,l.useState)(!0),[d,f]=(0,l.useState)(),[b,R]=(0,l.useState)();return(0,l.useEffect)(()=>{let x=s.fhirSearchUrl(n,t).toString();x!==r&&(u(x),s[e](n,t).then(S=>{c(!1),f(S),R(O.allOk)}).catch(S=>{c(!1),f(void 0),R((0,O.normalizeOperationOutcome)(S))}))},[s,e,n,t,r]),[d,i,b]}var U=require("@medplum/core"),o=require("react");var se=3e3;function ue(e,n,t){let s=m(),[r,u]=(0,o.useState)(),[i,c]=(0,o.useState)(t?.subscriptionProps),d=(0,o.useRef)(!1),f=(0,o.useRef)(),b=(0,o.useRef)(),R=(0,o.useRef)(),x=(0,o.useRef)();x.current=n;let S=(0,o.useRef)();S.current=t?.onWebSocketOpen;let W=(0,o.useRef)();W.current=t?.onWebSocketClose;let w=(0,o.useRef)();w.current=t?.onSubscriptionConnect;let F=(0,o.useRef)();F.current=t?.onSubscriptionDisconnect;let B=(0,o.useRef)();B.current=t?.onError,(0,o.useEffect)(()=>{(0,U.deepEquals)(t?.subscriptionProps,i)||c(t?.subscriptionProps)},[i,t]),(0,o.useEffect)(()=>{f.current&&(clearTimeout(f.current),f.current=void 0);let p=!1;return(b.current!==e||!(0,U.deepEquals)(R.current,i))&&(p=!0),p&&b.current&&s.unsubscribeFromCriteria(b.current,R.current),b.current=e,R.current=i,p&&e?u(s.subscribeToCriteria(e,i)):e||u(void 0),()=>{f.current=setTimeout(()=>{u(void 0),e&&s.unsubscribeFromCriteria(e,i)},se)}},[s,e,i]);let T=(0,o.useCallback)(p=>{x.current?.(p.payload)},[]),P=(0,o.useCallback)(()=>{S.current?.()},[]),y=(0,o.useCallback)(()=>{W.current?.()},[]),L=(0,o.useCallback)(p=>{w.current?.(p.payload.subscriptionId)},[]),K=(0,o.useCallback)(p=>{F.current?.(p.payload.subscriptionId)},[]),k=(0,o.useCallback)(p=>{B.current?.(p.payload)},[]);(0,o.useEffect)(()=>r?(d.current||(r.addEventListener("message",T),r.addEventListener("open",P),r.addEventListener("close",y),r.addEventListener("connect",L),r.addEventListener("disconnect",K),r.addEventListener("error",k),d.current=!0),()=>{d.current=!1,r.removeEventListener("message",T),r.removeEventListener("open",P),r.removeEventListener("close",y),r.removeEventListener("connect",L),r.removeEventListener("disconnect",K),r.removeEventListener("error",k)}):()=>{},[r,T,P,y,L,K,k])}
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/index.ts", "../../src/MedplumProvider/MedplumProvider.tsx", "../../src/MedplumProvider/MedplumProvider.context.ts", "../../src/useCachedBinaryUrl/useCachedBinaryUrl.ts", "../../src/usePrevious/usePrevious.ts", "../../src/useResource/useResource.ts", "../../src/useSearch/useSearch.ts", "../../src/useSubscription/useSubscription.ts"],
|
|
4
|
-
"sourcesContent": ["export * from './MedplumProvider/MedplumProvider';\nexport * from './MedplumProvider/MedplumProvider.context';\nexport * from './useCachedBinaryUrl/useCachedBinaryUrl';\nexport * from './usePrevious/usePrevious';\nexport * from './useResource/useResource';\nexport * from './useSearch/useSearch';\nexport * from './useSubscription/useSubscription';\n", "import { MedplumClient, MedplumClientEventMap } from '@medplum/core';\nimport { ReactNode, useEffect, useMemo, useState } from 'react';\nimport { MedplumNavigateFunction, reactContext } from './MedplumProvider.context';\n\nexport interface MedplumProviderProps {\n readonly medplum: MedplumClient;\n readonly navigate?: MedplumNavigateFunction;\n readonly children: ReactNode;\n}\n\nconst EVENTS_TO_TRACK = [\n 'change',\n 'storageInitialized',\n 'storageInitFailed',\n 'profileRefreshing',\n 'profileRefreshed',\n] satisfies (keyof MedplumClientEventMap)[];\n\n/**\n * The MedplumProvider component provides Medplum context state.\n *\n * Medplum context includes:\n * 1) medplum - Medplum client library\n * 2) profile - The current user profile (if signed in)\n * @param props - The MedplumProvider React props.\n * @returns The MedplumProvider React node.\n */\nexport function MedplumProvider(props: MedplumProviderProps): JSX.Element {\n const medplum = props.medplum;\n const navigate = props.navigate ?? defaultNavigate;\n\n const [state, setState] = useState({\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n });\n\n useEffect(() => {\n function eventListener(): void {\n setState((s) => ({\n ...s,\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n }));\n }\n\n for (const event of EVENTS_TO_TRACK) {\n medplum.addEventListener(event, eventListener);\n }\n return () => {\n for (const event of EVENTS_TO_TRACK) {\n medplum.removeEventListener(event, eventListener);\n }\n };\n }, [medplum]);\n\n const medplumContext = useMemo(\n () => ({\n ...state,\n medplum,\n navigate,\n }),\n [state, medplum, navigate]\n );\n\n return <reactContext.Provider value={medplumContext}>{props.children}</reactContext.Provider>;\n}\n\n/**\n * The default \"navigate\" function which simply uses window.location.href.\n * @param path - The path to navigate to.\n */\nfunction defaultNavigate(path: string): void {\n window.location.assign(path);\n}\n", "import { MedplumClient, ProfileResource } from '@medplum/core';\nimport { createContext, useContext } from 'react';\n\nexport const reactContext = createContext(undefined as MedplumContext | undefined);\n\nexport type MedplumNavigateFunction = (path: string) => void;\n\nexport interface MedplumContext {\n medplum: MedplumClient;\n navigate: MedplumNavigateFunction;\n profile?: ProfileResource;\n loading: boolean;\n}\n\n/**\n * Returns the MedplumContext instance.\n * @returns The MedplumContext instance.\n */\nexport function useMedplumContext(): MedplumContext {\n return useContext(reactContext) as MedplumContext;\n}\n\n/**\n * Returns the MedplumClient instance.\n * This is a shortcut for useMedplumContext().medplum.\n * @returns The MedplumClient instance.\n */\nexport function useMedplum(): MedplumClient {\n return useMedplumContext().medplum;\n}\n\n/**\n * Returns the Medplum navigate function.\n * @returns The Medplum navigate function.\n */\nexport function useMedplumNavigate(): MedplumNavigateFunction {\n return useMedplumContext().navigate;\n}\n\n/**\n * Returns the current Medplum user profile (if signed in).\n * This is a shortcut for useMedplumContext().profile.\n * @returns The current user profile.\n */\nexport function useMedplumProfile(): ProfileResource | undefined {\n return useMedplumContext().profile;\n}\n", "import { useMemo } from 'react';\n\n// Maintain a cache of urls to avoid unnecessary re-download of attachments\n// The following is a workaround for the fact that each request to a resource containing a Binary data reference\n// returns a NEW signed S3 URL for each bypassing the native browser caching mechanism\n// resulting in unnecessary bandwidth consumption.\n// https://www.medplum.com/docs/fhir-datastore/binary-data#consuming-a-fhir-binary-in-an-application\n// https://github.com/medplum/medplum/issues/3815\n\n// The S3 presigned URLs expire after 1 hour with the default configuration and hard refreshes are not uncommon even in SPAs so this\n// could be a good way to get additional cache hits\n// This would require additional logic for initialization, saving, and purging of expired keys\nconst urls = new Map<string, string>();\n\nexport const useCachedBinaryUrl = (binaryUrl: string | undefined): string | undefined => {\n return useMemo(() => {\n if (!binaryUrl) {\n return undefined;\n }\n\n const binaryResourceUrl = binaryUrl.split('?')[0];\n if (!binaryResourceUrl) {\n return binaryUrl;\n }\n\n // Check if the binaryUrl is a presigned S3 URL\n let binaryUrlSearchParams: URLSearchParams;\n try {\n binaryUrlSearchParams = new URLSearchParams(new URL(binaryUrl).search);\n } catch (_err) {\n return binaryUrl;\n }\n\n if (!binaryUrlSearchParams.has('Key-Pair-Id') || !binaryUrlSearchParams.has('Signature')) {\n return binaryUrl;\n }\n\n // https://stackoverflow.com/questions/23929145/how-to-test-if-a-given-time-stamp-is-in-seconds-or-milliseconds\n const binaryUrlExpires = binaryUrlSearchParams.get('Expires');\n if (!binaryUrlExpires || binaryUrlExpires.length > 13) {\n // Expires is expected to be in seconds, not milliseconds\n return binaryUrl;\n }\n\n const cachedUrl = urls.get(binaryResourceUrl);\n if (cachedUrl) {\n const searchParams = new URLSearchParams(new URL(cachedUrl).search);\n\n // This is fairly brittle as it relies on the current structure of the Medplum returned URL\n const expires = searchParams.get('Expires');\n\n // `expires` is in seconds, Date.now() is in ms\n // Add padding to mitigate expiration between time of check and time of use\n if (expires && parseInt(expires, 10) * 1000 - 5_000 > Date.now()) {\n return cachedUrl;\n }\n }\n\n urls.set(binaryResourceUrl, binaryUrl);\n return binaryUrl;\n }, [binaryUrl]);\n};\n", "import { useEffect, useRef } from 'react';\n\n/**\n * React Hook to keep track of the passed-in value from the previous render of the containing component.\n * @param value - The value to track.\n * @returns The value passed in from the previous render.\n */\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>();\n useEffect(() => {\n ref.current = value;\n });\n return ref.current;\n}\n", "import { deepEquals, isReference, isResource, MedplumClient, normalizeOperationOutcome } from '@medplum/core';\nimport { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * React Hook to use a FHIR reference.\n * Handles the complexity of resolving references and caching resources.\n * @param value - The resource or reference to resource.\n * @param setOutcome - Optional callback to set the OperationOutcome.\n * @returns The resolved resource.\n */\nexport function useResource<T extends Resource>(\n value: Reference<T> | Partial<T> | undefined,\n setOutcome?: (outcome: OperationOutcome) => void\n): T | undefined {\n const medplum = useMedplum();\n const [resource, setResource] = useState<T | undefined>(() => {\n return getInitialResource(medplum, value);\n });\n\n const setResourceIfChanged = useCallback(\n (r: T | undefined) => {\n if (!deepEquals(r, resource)) {\n setResource(r);\n }\n },\n [resource]\n );\n\n useEffect(() => {\n let subscribed = true;\n\n const newValue = getInitialResource(medplum, value);\n if (!newValue && isReference(value)) {\n medplum\n .readReference(value as Reference<T>)\n .then((r) => {\n if (subscribed) {\n setResourceIfChanged(r);\n }\n })\n .catch((err) => {\n if (subscribed) {\n setResourceIfChanged(undefined);\n if (setOutcome) {\n setOutcome(normalizeOperationOutcome(err));\n }\n }\n });\n } else {\n setResourceIfChanged(newValue);\n }\n\n return (() => (subscribed = false)) as () => void;\n }, [medplum, value, setResourceIfChanged, setOutcome]);\n\n return resource;\n}\n\n/**\n * Returns the initial resource value based on the input value.\n * If the input value is a resource, returns the resource.\n * If the input value is a reference to a resource available in the cache, returns the resource.\n * Otherwise, returns undefined.\n * @param medplum - The medplum client.\n * @param value - The resource or reference to resource.\n * @returns An initial resource if available; undefined otherwise.\n */\nfunction getInitialResource<T extends Resource>(\n medplum: MedplumClient,\n value: Reference<T> | Partial<T> | undefined\n): T | undefined {\n if (value) {\n if (isResource(value)) {\n return value as T;\n }\n\n if (isReference(value)) {\n return medplum.getCachedReference(value as Reference<T>);\n }\n }\n\n return undefined;\n}\n", "import { allOk, normalizeOperationOutcome, QueryTypes, ResourceArray } from '@medplum/core';\nimport { Bundle, ExtractResource, OperationOutcome, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\n\n/**\n * React hook for searching FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.search() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearch<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query);\n}\n\n/**\n * React hook for searching for a single FHIR resource.\n *\n * This is a convenience hook for calling the MedplumClient.searchOne() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchOne<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query);\n}\n\n/**\n * React hook for searching for an array of FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.searchResources() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchResources<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query);\n}\n\nfunction useSearchImpl<K extends ResourceType, ReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined\n): [ReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [searchKey, setSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(true);\n const [result, setResult] = useState<ReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n useEffect(() => {\n const key = medplum.fhirSearchUrl(resourceType, query).toString();\n if (key !== searchKey) {\n setSearchKey(key);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as ReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, searchKey]);\n\n return [result, loading, outcome];\n}\n", "import { SubscriptionEmitter, SubscriptionEventMap, deepEquals } from '@medplum/core';\nimport { Bundle, Subscription } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nconst SUBSCRIPTION_DEBOUNCE_MS = 3000;\n\nexport type UseSubscriptionOptions = {\n subscriptionProps?: Partial<Subscription>;\n onWebSocketOpen?: () => void;\n onWebSocketClose?: () => void;\n onSubscriptionConnect?: (subscriptionId: string) => void;\n onSubscriptionDisconnect?: (subscriptionId: string) => void;\n onError?: (err: Error) => void;\n};\n\n/**\n * Creates an in-memory `Subscription` resource with the given criteria on the Medplum server and calls the given callback when an event notification is triggered by a resource interaction over a WebSocket connection.\n *\n * Subscriptions created with this hook are lightweight, share a single WebSocket connection, and are automatically untracked and cleaned up when the containing component is no longer mounted.\n *\n * @param criteria - The FHIR search criteria to subscribe to.\n * @param callback - The callback to call when a notification event `Bundle` for this `Subscription` is received.\n * @param options - Optional options used to configure the created `Subscription`. See {@link UseSubscriptionOptions}\n *\n * --------------------------------------------------------------------------------------------------------------------------------\n *\n * `options` contains the following properties, all of which are optional:\n * - `subscriptionProps` - Allows the caller to pass a `Partial<Subscription>` to use as part of the creation\n * of the `Subscription` resource for this subscription. It enables the user namely to pass things like the `extension` property and to create\n * the `Subscription` with extensions such the {@link https://www.medplum.com/docs/subscriptions/subscription-extensions#interactions \"Supported Interaction\"} extension which would enable to listen for `create` or `update` only events.\n * - `onWebsocketOpen` - Called when the WebSocket connection is established with Medplum server.\n * - `onWebsocketClose` - Called when the WebSocket connection disconnects.\n * - `onSubscriptionConnect` - Called when the corresponding subscription starts to receive updates after the subscription has been initialized and connected to.\n * - `onSubscriptionDisconnect` - Called when the corresponding subscription is destroyed and stops receiving updates from the server.\n * - `onError` - Called whenever an error occurs during the lifecycle of the managed subscription.\n */\nexport function useSubscription(\n criteria: string,\n callback: (bundle: Bundle) => void,\n options?: UseSubscriptionOptions\n): void {\n const medplum = useMedplum();\n const [emitter, setEmitter] = useState<SubscriptionEmitter>();\n // We don't memoize the entire options object since it contains callbacks and if the callbacks change identity, we don't want to trigger a resubscribe to criteria\n const [memoizedSubProps, setMemoizedSubProps] = useState(options?.subscriptionProps);\n\n const listeningRef = useRef(false);\n const unsubTimerRef = useRef<ReturnType<typeof setTimeout>>();\n\n const prevCriteriaRef = useRef<string>();\n const prevMemoizedSubPropsRef = useRef<UseSubscriptionOptions['subscriptionProps']>();\n\n const callbackRef = useRef<typeof callback>();\n callbackRef.current = callback;\n\n const onWebSocketOpenRef = useRef<UseSubscriptionOptions['onWebSocketOpen']>();\n onWebSocketOpenRef.current = options?.onWebSocketOpen;\n\n const onWebSocketCloseRef = useRef<UseSubscriptionOptions['onWebSocketClose']>();\n onWebSocketCloseRef.current = options?.onWebSocketClose;\n\n const onSubscriptionConnectRef = useRef<UseSubscriptionOptions['onSubscriptionConnect']>();\n onSubscriptionConnectRef.current = options?.onSubscriptionConnect;\n\n const onSubscriptionDisconnectRef = useRef<UseSubscriptionOptions['onSubscriptionDisconnect']>();\n onSubscriptionDisconnectRef.current = options?.onSubscriptionDisconnect;\n\n const onErrorRef = useRef<UseSubscriptionOptions['onError']>();\n onErrorRef.current = options?.onError;\n\n useEffect(() => {\n // Deep equals checks referential equality first\n if (!deepEquals(options?.subscriptionProps, memoizedSubProps)) {\n setMemoizedSubProps(options?.subscriptionProps);\n }\n }, [memoizedSubProps, options]);\n\n useEffect(() => {\n if (unsubTimerRef.current) {\n clearTimeout(unsubTimerRef.current);\n unsubTimerRef.current = undefined;\n }\n\n let shouldSubscribe = false;\n if (prevCriteriaRef.current !== criteria || !deepEquals(prevMemoizedSubPropsRef.current, memoizedSubProps)) {\n shouldSubscribe = true;\n }\n\n if (shouldSubscribe && prevCriteriaRef.current) {\n medplum.unsubscribeFromCriteria(prevCriteriaRef.current, prevMemoizedSubPropsRef.current);\n }\n\n // Set prev criteria and options to latest after checking them\n prevCriteriaRef.current = criteria;\n prevMemoizedSubPropsRef.current = memoizedSubProps;\n\n // We do this after as to not immediately trigger re-render\n if (shouldSubscribe) {\n setEmitter(medplum.subscribeToCriteria(criteria, memoizedSubProps));\n }\n\n return () => {\n unsubTimerRef.current = setTimeout(() => {\n setEmitter(undefined);\n medplum.unsubscribeFromCriteria(criteria, memoizedSubProps);\n }, SUBSCRIPTION_DEBOUNCE_MS);\n };\n }, [medplum, criteria, memoizedSubProps]);\n\n const emitterCallback = useCallback((event: SubscriptionEventMap['message']) => {\n callbackRef.current?.(event.payload);\n }, []);\n\n const onWebSocketOpen = useCallback(() => {\n onWebSocketOpenRef.current?.();\n }, []);\n\n const onWebSocketClose = useCallback(() => {\n onWebSocketCloseRef.current?.();\n }, []);\n\n const onSubscriptionConnect = useCallback((event: SubscriptionEventMap['connect']) => {\n onSubscriptionConnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onSubscriptionDisconnect = useCallback((event: SubscriptionEventMap['disconnect']) => {\n onSubscriptionDisconnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onError = useCallback((event: SubscriptionEventMap['error']) => {\n onErrorRef.current?.(event.payload);\n }, []);\n\n useEffect(() => {\n if (!emitter) {\n return () => undefined;\n }\n if (!listeningRef.current) {\n emitter.addEventListener('message', emitterCallback);\n emitter.addEventListener('open', onWebSocketOpen);\n emitter.addEventListener('close', onWebSocketClose);\n emitter.addEventListener('connect', onSubscriptionConnect);\n emitter.addEventListener('disconnect', onSubscriptionDisconnect);\n emitter.addEventListener('error', onError);\n listeningRef.current = true;\n }\n return () => {\n listeningRef.current = false;\n emitter.removeEventListener('message', emitterCallback);\n emitter.removeEventListener('open', onWebSocketOpen);\n emitter.removeEventListener('close', onWebSocketClose);\n emitter.removeEventListener('connect', onSubscriptionConnect);\n emitter.removeEventListener('disconnect', onSubscriptionDisconnect);\n emitter.removeEventListener('error', onError);\n };\n }, [\n emitter,\n emitterCallback,\n onWebSocketOpen,\n onWebSocketClose,\n onSubscriptionConnect,\n onSubscriptionDisconnect,\n onError,\n ]);\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["export * from './MedplumProvider/MedplumProvider';\nexport * from './MedplumProvider/MedplumProvider.context';\nexport * from './useCachedBinaryUrl/useCachedBinaryUrl';\nexport * from './usePrevious/usePrevious';\nexport * from './useResource/useResource';\nexport * from './useSearch/useSearch';\nexport * from './useSubscription/useSubscription';\n", "import { MedplumClient, MedplumClientEventMap } from '@medplum/core';\nimport { ReactNode, useEffect, useMemo, useState } from 'react';\nimport { MedplumNavigateFunction, reactContext } from './MedplumProvider.context';\n\nexport interface MedplumProviderProps {\n readonly medplum: MedplumClient;\n readonly navigate?: MedplumNavigateFunction;\n readonly children: ReactNode;\n}\n\nconst EVENTS_TO_TRACK = [\n 'change',\n 'storageInitialized',\n 'storageInitFailed',\n 'profileRefreshing',\n 'profileRefreshed',\n] satisfies (keyof MedplumClientEventMap)[];\n\n/**\n * The MedplumProvider component provides Medplum context state.\n *\n * Medplum context includes:\n * 1) medplum - Medplum client library\n * 2) profile - The current user profile (if signed in)\n * @param props - The MedplumProvider React props.\n * @returns The MedplumProvider React node.\n */\nexport function MedplumProvider(props: MedplumProviderProps): JSX.Element {\n const medplum = props.medplum;\n const navigate = props.navigate ?? defaultNavigate;\n\n const [state, setState] = useState({\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n });\n\n useEffect(() => {\n function eventListener(): void {\n setState((s) => ({\n ...s,\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n }));\n }\n\n for (const event of EVENTS_TO_TRACK) {\n medplum.addEventListener(event, eventListener);\n }\n return () => {\n for (const event of EVENTS_TO_TRACK) {\n medplum.removeEventListener(event, eventListener);\n }\n };\n }, [medplum]);\n\n const medplumContext = useMemo(\n () => ({\n ...state,\n medplum,\n navigate,\n }),\n [state, medplum, navigate]\n );\n\n return <reactContext.Provider value={medplumContext}>{props.children}</reactContext.Provider>;\n}\n\n/**\n * The default \"navigate\" function which simply uses window.location.href.\n * @param path - The path to navigate to.\n */\nfunction defaultNavigate(path: string): void {\n window.location.assign(path);\n}\n", "import { MedplumClient, ProfileResource } from '@medplum/core';\nimport { createContext, useContext } from 'react';\n\nexport const reactContext = createContext(undefined as MedplumContext | undefined);\n\nexport type MedplumNavigateFunction = (path: string) => void;\n\nexport interface MedplumContext {\n medplum: MedplumClient;\n navigate: MedplumNavigateFunction;\n profile?: ProfileResource;\n loading: boolean;\n}\n\n/**\n * Returns the MedplumContext instance.\n * @returns The MedplumContext instance.\n */\nexport function useMedplumContext(): MedplumContext {\n return useContext(reactContext) as MedplumContext;\n}\n\n/**\n * Returns the MedplumClient instance.\n * This is a shortcut for useMedplumContext().medplum.\n * @returns The MedplumClient instance.\n */\nexport function useMedplum(): MedplumClient {\n return useMedplumContext().medplum;\n}\n\n/**\n * Returns the Medplum navigate function.\n * @returns The Medplum navigate function.\n */\nexport function useMedplumNavigate(): MedplumNavigateFunction {\n return useMedplumContext().navigate;\n}\n\n/**\n * Returns the current Medplum user profile (if signed in).\n * This is a shortcut for useMedplumContext().profile.\n * @returns The current user profile.\n */\nexport function useMedplumProfile(): ProfileResource | undefined {\n return useMedplumContext().profile;\n}\n", "import { useMemo } from 'react';\n\n// Maintain a cache of urls to avoid unnecessary re-download of attachments\n// The following is a workaround for the fact that each request to a resource containing a Binary data reference\n// returns a NEW signed S3 URL for each bypassing the native browser caching mechanism\n// resulting in unnecessary bandwidth consumption.\n// https://www.medplum.com/docs/fhir-datastore/binary-data#consuming-a-fhir-binary-in-an-application\n// https://github.com/medplum/medplum/issues/3815\n\n// The S3 presigned URLs expire after 1 hour with the default configuration and hard refreshes are not uncommon even in SPAs so this\n// could be a good way to get additional cache hits\n// This would require additional logic for initialization, saving, and purging of expired keys\nconst urls = new Map<string, string>();\n\nexport const useCachedBinaryUrl = (binaryUrl: string | undefined): string | undefined => {\n return useMemo(() => {\n if (!binaryUrl) {\n return undefined;\n }\n\n const binaryResourceUrl = binaryUrl.split('?')[0];\n if (!binaryResourceUrl) {\n return binaryUrl;\n }\n\n // Check if the binaryUrl is a presigned S3 URL\n let binaryUrlSearchParams: URLSearchParams;\n try {\n binaryUrlSearchParams = new URLSearchParams(new URL(binaryUrl).search);\n } catch (_err) {\n return binaryUrl;\n }\n\n if (!binaryUrlSearchParams.has('Key-Pair-Id') || !binaryUrlSearchParams.has('Signature')) {\n return binaryUrl;\n }\n\n // https://stackoverflow.com/questions/23929145/how-to-test-if-a-given-time-stamp-is-in-seconds-or-milliseconds\n const binaryUrlExpires = binaryUrlSearchParams.get('Expires');\n if (!binaryUrlExpires || binaryUrlExpires.length > 13) {\n // Expires is expected to be in seconds, not milliseconds\n return binaryUrl;\n }\n\n const cachedUrl = urls.get(binaryResourceUrl);\n if (cachedUrl) {\n const searchParams = new URLSearchParams(new URL(cachedUrl).search);\n\n // This is fairly brittle as it relies on the current structure of the Medplum returned URL\n const expires = searchParams.get('Expires');\n\n // `expires` is in seconds, Date.now() is in ms\n // Add padding to mitigate expiration between time of check and time of use\n if (expires && parseInt(expires, 10) * 1000 - 5_000 > Date.now()) {\n return cachedUrl;\n }\n }\n\n urls.set(binaryResourceUrl, binaryUrl);\n return binaryUrl;\n }, [binaryUrl]);\n};\n", "import { useEffect, useRef } from 'react';\n\n/**\n * React Hook to keep track of the passed-in value from the previous render of the containing component.\n * @param value - The value to track.\n * @returns The value passed in from the previous render.\n */\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>();\n useEffect(() => {\n ref.current = value;\n });\n return ref.current;\n}\n", "import { deepEquals, isReference, isResource, MedplumClient, normalizeOperationOutcome } from '@medplum/core';\nimport { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * React Hook to use a FHIR reference.\n * Handles the complexity of resolving references and caching resources.\n * @param value - The resource or reference to resource.\n * @param setOutcome - Optional callback to set the OperationOutcome.\n * @returns The resolved resource.\n */\nexport function useResource<T extends Resource>(\n value: Reference<T> | Partial<T> | undefined,\n setOutcome?: (outcome: OperationOutcome) => void\n): T | undefined {\n const medplum = useMedplum();\n const [resource, setResource] = useState<T | undefined>(() => {\n return getInitialResource(medplum, value);\n });\n\n const setResourceIfChanged = useCallback(\n (r: T | undefined) => {\n if (!deepEquals(r, resource)) {\n setResource(r);\n }\n },\n [resource]\n );\n\n useEffect(() => {\n let subscribed = true;\n\n const newValue = getInitialResource(medplum, value);\n if (!newValue && isReference(value)) {\n medplum\n .readReference(value as Reference<T>)\n .then((r) => {\n if (subscribed) {\n setResourceIfChanged(r);\n }\n })\n .catch((err) => {\n if (subscribed) {\n setResourceIfChanged(undefined);\n if (setOutcome) {\n setOutcome(normalizeOperationOutcome(err));\n }\n }\n });\n } else {\n setResourceIfChanged(newValue);\n }\n\n return (() => (subscribed = false)) as () => void;\n }, [medplum, value, setResourceIfChanged, setOutcome]);\n\n return resource;\n}\n\n/**\n * Returns the initial resource value based on the input value.\n * If the input value is a resource, returns the resource.\n * If the input value is a reference to a resource available in the cache, returns the resource.\n * Otherwise, returns undefined.\n * @param medplum - The medplum client.\n * @param value - The resource or reference to resource.\n * @returns An initial resource if available; undefined otherwise.\n */\nfunction getInitialResource<T extends Resource>(\n medplum: MedplumClient,\n value: Reference<T> | Partial<T> | undefined\n): T | undefined {\n if (value) {\n if (isResource(value)) {\n return value as T;\n }\n\n if (isReference(value)) {\n return medplum.getCachedReference(value as Reference<T>);\n }\n }\n\n return undefined;\n}\n", "import { allOk, normalizeOperationOutcome, QueryTypes, ResourceArray } from '@medplum/core';\nimport { Bundle, ExtractResource, OperationOutcome, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\n\n/**\n * React hook for searching FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.search() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearch<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query);\n}\n\n/**\n * React hook for searching for a single FHIR resource.\n *\n * This is a convenience hook for calling the MedplumClient.searchOne() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchOne<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query);\n}\n\n/**\n * React hook for searching for an array of FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.searchResources() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchResources<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query);\n}\n\nfunction useSearchImpl<K extends ResourceType, ReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined\n): [ReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [searchKey, setSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(true);\n const [result, setResult] = useState<ReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n useEffect(() => {\n const key = medplum.fhirSearchUrl(resourceType, query).toString();\n if (key !== searchKey) {\n setSearchKey(key);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as ReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, searchKey]);\n\n return [result, loading, outcome];\n}\n", "import { SubscriptionEmitter, SubscriptionEventMap, deepEquals } from '@medplum/core';\nimport { Bundle, Subscription } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nconst SUBSCRIPTION_DEBOUNCE_MS = 3000;\n\nexport type UseSubscriptionOptions = {\n subscriptionProps?: Partial<Subscription>;\n onWebSocketOpen?: () => void;\n onWebSocketClose?: () => void;\n onSubscriptionConnect?: (subscriptionId: string) => void;\n onSubscriptionDisconnect?: (subscriptionId: string) => void;\n onError?: (err: Error) => void;\n};\n\n/**\n * Creates an in-memory `Subscription` resource with the given criteria on the Medplum server and calls the given callback when an event notification is triggered by a resource interaction over a WebSocket connection.\n *\n * Subscriptions created with this hook are lightweight, share a single WebSocket connection, and are automatically untracked and cleaned up when the containing component is no longer mounted.\n *\n * @param criteria - The FHIR search criteria to subscribe to.\n * @param callback - The callback to call when a notification event `Bundle` for this `Subscription` is received.\n * @param options - Optional options used to configure the created `Subscription`. See {@link UseSubscriptionOptions}\n *\n * --------------------------------------------------------------------------------------------------------------------------------\n *\n * `options` contains the following properties, all of which are optional:\n * - `subscriptionProps` - Allows the caller to pass a `Partial<Subscription>` to use as part of the creation\n * of the `Subscription` resource for this subscription. It enables the user namely to pass things like the `extension` property and to create\n * the `Subscription` with extensions such the {@link https://www.medplum.com/docs/subscriptions/subscription-extensions#interactions \"Supported Interaction\"} extension which would enable to listen for `create` or `update` only events.\n * - `onWebsocketOpen` - Called when the WebSocket connection is established with Medplum server.\n * - `onWebsocketClose` - Called when the WebSocket connection disconnects.\n * - `onSubscriptionConnect` - Called when the corresponding subscription starts to receive updates after the subscription has been initialized and connected to.\n * - `onSubscriptionDisconnect` - Called when the corresponding subscription is destroyed and stops receiving updates from the server.\n * - `onError` - Called whenever an error occurs during the lifecycle of the managed subscription.\n */\nexport function useSubscription(\n criteria: string | undefined,\n callback: (bundle: Bundle) => void,\n options?: UseSubscriptionOptions\n): void {\n const medplum = useMedplum();\n const [emitter, setEmitter] = useState<SubscriptionEmitter>();\n // We don't memoize the entire options object since it contains callbacks and if the callbacks change identity, we don't want to trigger a resubscribe to criteria\n const [memoizedSubProps, setMemoizedSubProps] = useState(options?.subscriptionProps);\n\n const listeningRef = useRef(false);\n const unsubTimerRef = useRef<ReturnType<typeof setTimeout>>();\n\n const prevCriteriaRef = useRef<string | undefined>();\n const prevMemoizedSubPropsRef = useRef<UseSubscriptionOptions['subscriptionProps']>();\n\n const callbackRef = useRef<typeof callback>();\n callbackRef.current = callback;\n\n const onWebSocketOpenRef = useRef<UseSubscriptionOptions['onWebSocketOpen']>();\n onWebSocketOpenRef.current = options?.onWebSocketOpen;\n\n const onWebSocketCloseRef = useRef<UseSubscriptionOptions['onWebSocketClose']>();\n onWebSocketCloseRef.current = options?.onWebSocketClose;\n\n const onSubscriptionConnectRef = useRef<UseSubscriptionOptions['onSubscriptionConnect']>();\n onSubscriptionConnectRef.current = options?.onSubscriptionConnect;\n\n const onSubscriptionDisconnectRef = useRef<UseSubscriptionOptions['onSubscriptionDisconnect']>();\n onSubscriptionDisconnectRef.current = options?.onSubscriptionDisconnect;\n\n const onErrorRef = useRef<UseSubscriptionOptions['onError']>();\n onErrorRef.current = options?.onError;\n\n useEffect(() => {\n // Deep equals checks referential equality first\n if (!deepEquals(options?.subscriptionProps, memoizedSubProps)) {\n setMemoizedSubProps(options?.subscriptionProps);\n }\n }, [memoizedSubProps, options]);\n\n useEffect(() => {\n if (unsubTimerRef.current) {\n clearTimeout(unsubTimerRef.current);\n unsubTimerRef.current = undefined;\n }\n\n let shouldSubscribe = false;\n if (prevCriteriaRef.current !== criteria || !deepEquals(prevMemoizedSubPropsRef.current, memoizedSubProps)) {\n shouldSubscribe = true;\n }\n\n if (shouldSubscribe && prevCriteriaRef.current) {\n medplum.unsubscribeFromCriteria(prevCriteriaRef.current, prevMemoizedSubPropsRef.current);\n }\n\n // Set prev criteria and options to latest after checking them\n prevCriteriaRef.current = criteria;\n prevMemoizedSubPropsRef.current = memoizedSubProps;\n\n // We do this after as to not immediately trigger re-render\n if (shouldSubscribe && criteria) {\n setEmitter(medplum.subscribeToCriteria(criteria, memoizedSubProps));\n } else if (!criteria) {\n setEmitter(undefined);\n }\n\n return () => {\n unsubTimerRef.current = setTimeout(() => {\n setEmitter(undefined);\n if (criteria) {\n medplum.unsubscribeFromCriteria(criteria, memoizedSubProps);\n }\n }, SUBSCRIPTION_DEBOUNCE_MS);\n };\n }, [medplum, criteria, memoizedSubProps]);\n\n const emitterCallback = useCallback((event: SubscriptionEventMap['message']) => {\n callbackRef.current?.(event.payload);\n }, []);\n\n const onWebSocketOpen = useCallback(() => {\n onWebSocketOpenRef.current?.();\n }, []);\n\n const onWebSocketClose = useCallback(() => {\n onWebSocketCloseRef.current?.();\n }, []);\n\n const onSubscriptionConnect = useCallback((event: SubscriptionEventMap['connect']) => {\n onSubscriptionConnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onSubscriptionDisconnect = useCallback((event: SubscriptionEventMap['disconnect']) => {\n onSubscriptionDisconnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onError = useCallback((event: SubscriptionEventMap['error']) => {\n onErrorRef.current?.(event.payload);\n }, []);\n\n useEffect(() => {\n if (!emitter) {\n return () => undefined;\n }\n if (!listeningRef.current) {\n emitter.addEventListener('message', emitterCallback);\n emitter.addEventListener('open', onWebSocketOpen);\n emitter.addEventListener('close', onWebSocketClose);\n emitter.addEventListener('connect', onSubscriptionConnect);\n emitter.addEventListener('disconnect', onSubscriptionDisconnect);\n emitter.addEventListener('error', onError);\n listeningRef.current = true;\n }\n return () => {\n listeningRef.current = false;\n emitter.removeEventListener('message', emitterCallback);\n emitter.removeEventListener('open', onWebSocketOpen);\n emitter.removeEventListener('close', onWebSocketClose);\n emitter.removeEventListener('connect', onSubscriptionConnect);\n emitter.removeEventListener('disconnect', onSubscriptionDisconnect);\n emitter.removeEventListener('error', onError);\n };\n }, [\n emitter,\n emitterCallback,\n onWebSocketOpen,\n onWebSocketClose,\n onSubscriptionConnect,\n onSubscriptionDisconnect,\n onError,\n ]);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["src_exports", "__export", "MedplumProvider", "reactContext", "useCachedBinaryUrl", "useMedplum", "useMedplumContext", "useMedplumNavigate", "useMedplumProfile", "usePrevious", "useResource", "useSearch", "useSearchOne", "useSearchResources", "useSubscription", "__toCommonJS", "import_react", "import_react", "reactContext", "useMedplumContext", "useMedplum", "useMedplumNavigate", "useMedplumProfile", "import_jsx_runtime", "EVENTS_TO_TRACK", "MedplumProvider", "props", "medplum", "navigate", "defaultNavigate", "state", "setState", "eventListener", "s", "event", "medplumContext", "reactContext", "path", "import_react", "urls", "useCachedBinaryUrl", "binaryUrl", "binaryResourceUrl", "binaryUrlSearchParams", "binaryUrlExpires", "cachedUrl", "expires", "import_react", "usePrevious", "value", "ref", "import_core", "import_react", "useResource", "value", "setOutcome", "medplum", "useMedplum", "resource", "setResource", "getInitialResource", "setResourceIfChanged", "r", "subscribed", "newValue", "err", "import_core", "import_react", "useSearch", "resourceType", "query", "useSearchImpl", "useSearchOne", "useSearchResources", "searchFn", "medplum", "useMedplum", "searchKey", "setSearchKey", "loading", "setLoading", "result", "setResult", "outcome", "setOutcome", "key", "res", "err", "import_core", "import_react", "SUBSCRIPTION_DEBOUNCE_MS", "useSubscription", "criteria", "callback", "options", "medplum", "useMedplum", "emitter", "setEmitter", "memoizedSubProps", "setMemoizedSubProps", "listeningRef", "unsubTimerRef", "prevCriteriaRef", "prevMemoizedSubPropsRef", "callbackRef", "onWebSocketOpenRef", "onWebSocketCloseRef", "onSubscriptionConnectRef", "onSubscriptionDisconnectRef", "onErrorRef", "shouldSubscribe", "emitterCallback", "event", "onWebSocketOpen", "onWebSocketClose", "onSubscriptionConnect", "onSubscriptionDisconnect", "onError"]
|
|
7
7
|
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -138,7 +138,7 @@ export declare function useSearchResources<K extends ResourceType>(resourceType:
|
|
|
138
138
|
* - `onSubscriptionDisconnect` - Called when the corresponding subscription is destroyed and stops receiving updates from the server.
|
|
139
139
|
* - `onError` - Called whenever an error occurs during the lifecycle of the managed subscription.
|
|
140
140
|
*/
|
|
141
|
-
export declare function useSubscription(criteria: string, callback: (bundle: Bundle) => void, options?: UseSubscriptionOptions): void;
|
|
141
|
+
export declare function useSubscription(criteria: string | undefined, callback: (bundle: Bundle) => void, options?: UseSubscriptionOptions): void;
|
|
142
142
|
|
|
143
143
|
export declare type UseSubscriptionOptions = {
|
|
144
144
|
subscriptionProps?: Partial<Subscription>;
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -138,7 +138,7 @@ export declare function useSearchResources<K extends ResourceType>(resourceType:
|
|
|
138
138
|
* - `onSubscriptionDisconnect` - Called when the corresponding subscription is destroyed and stops receiving updates from the server.
|
|
139
139
|
* - `onError` - Called whenever an error occurs during the lifecycle of the managed subscription.
|
|
140
140
|
*/
|
|
141
|
-
export declare function useSubscription(criteria: string, callback: (bundle: Bundle) => void, options?: UseSubscriptionOptions): void;
|
|
141
|
+
export declare function useSubscription(criteria: string | undefined, callback: (bundle: Bundle) => void, options?: UseSubscriptionOptions): void;
|
|
142
142
|
|
|
143
143
|
export declare type UseSubscriptionOptions = {
|
|
144
144
|
subscriptionProps?: Partial<Subscription>;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useEffect as _,useMemo as Q,useState as A}from"react";import{createContext as D,useContext as z}from"react";var O=D(void 0);function T(){return z(O)}function b(){return T().medplum}function se(){return T().navigate}function ue(){return T().profile}import{jsx as J}from"react/jsx-runtime";var N=["change","storageInitialized","storageInitFailed","profileRefreshing","profileRefreshed"];function me(e){let
|
|
1
|
+
import{useEffect as _,useMemo as Q,useState as A}from"react";import{createContext as D,useContext as z}from"react";var O=D(void 0);function T(){return z(O)}function b(){return T().medplum}function se(){return T().navigate}function ue(){return T().profile}import{jsx as J}from"react/jsx-runtime";var N=["change","storageInitialized","storageInitFailed","profileRefreshing","profileRefreshed"];function me(e){let n=e.medplum,t=e.navigate??V,[i,r]=A({profile:n.getProfile(),loading:n.isLoading()});_(()=>{function o(){r(u=>({...u,profile:n.getProfile(),loading:n.isLoading()}))}for(let u of N)n.addEventListener(u,o);return()=>{for(let u of N)n.removeEventListener(u,o)}},[n]);let s=Q(()=>({...i,medplum:n,navigate:t}),[i,n,t]);return J(O.Provider,{value:s,children:e.children})}function V(e){window.location.assign(e)}import{useMemo as X}from"react";var U=new Map,Se=e=>X(()=>{if(!e)return;let n=e.split("?")[0];if(!n)return e;let t;try{t=new URLSearchParams(new URL(e).search)}catch{return e}if(!t.has("Key-Pair-Id")||!t.has("Signature"))return e;let i=t.get("Expires");if(!i||i.length>13)return e;let r=U.get(n);if(r){let o=new URLSearchParams(new URL(r).search).get("Expires");if(o&&parseInt(o,10)*1e3-5e3>Date.now())return r}return U.set(n,e),e},[e]);import{useEffect as q,useRef as G}from"react";function xe(e){let n=G();return q(()=>{n.current=e}),n.current}import{deepEquals as H,isReference as w,isResource as Y,normalizeOperationOutcome as Z}from"@medplum/core";import{useCallback as $,useEffect as j,useState as ee}from"react";function Te(e,n){let t=b(),[i,r]=ee(()=>W(t,e)),s=$(o=>{H(o,i)||r(o)},[i]);return j(()=>{let o=!0,u=W(t,e);return!u&&w(e)?t.readReference(e).then(d=>{o&&s(d)}).catch(d=>{o&&(s(void 0),n&&n(Z(d)))}):s(u),()=>o=!1},[t,e,s,n]),i}function W(e,n){if(n){if(Y(n))return n;if(w(n))return e.getCachedReference(n)}}import{allOk as ne,normalizeOperationOutcome as te}from"@medplum/core";import{useEffect as re,useState as v}from"react";function Ne(e,n){return P("search",e,n)}function Ue(e,n){return P("searchOne",e,n)}function We(e,n){return P("searchResources",e,n)}function P(e,n,t){let i=b(),[r,s]=v(),[o,u]=v(!0),[d,a]=v(),[f,m]=v();return re(()=>{let S=i.fhirSearchUrl(n,t).toString();S!==r&&(s(S),i[e](n,t).then(l=>{u(!1),a(l),m(ne)}).catch(l=>{u(!1),a(void 0),m(te(l))}))},[i,e,n,t,r]),[d,o,f]}import{deepEquals as F}from"@medplum/core";import{useCallback as R,useEffect as y,useRef as p,useState as B}from"react";var oe=3e3;function Qe(e,n,t){let i=b(),[r,s]=B(),[o,u]=B(t?.subscriptionProps),d=p(!1),a=p(),f=p(),m=p(),S=p();S.current=n;let l=p();l.current=t?.onWebSocketOpen;let L=p();L.current=t?.onWebSocketClose;let K=p();K.current=t?.onSubscriptionConnect;let k=p();k.current=t?.onSubscriptionDisconnect;let I=p();I.current=t?.onError,y(()=>{F(t?.subscriptionProps,o)||u(t?.subscriptionProps)},[o,t]),y(()=>{a.current&&(clearTimeout(a.current),a.current=void 0);let c=!1;return(f.current!==e||!F(m.current,o))&&(c=!0),c&&f.current&&i.unsubscribeFromCriteria(f.current,m.current),f.current=e,m.current=o,c&&e?s(i.subscribeToCriteria(e,o)):e||s(void 0),()=>{a.current=setTimeout(()=>{s(void 0),e&&i.unsubscribeFromCriteria(e,o)},oe)}},[i,e,o]);let E=R(c=>{S.current?.(c.payload)},[]),x=R(()=>{l.current?.()},[]),g=R(()=>{L.current?.()},[]),C=R(c=>{K.current?.(c.payload.subscriptionId)},[]),h=R(c=>{k.current?.(c.payload.subscriptionId)},[]),M=R(c=>{I.current?.(c.payload)},[]);y(()=>r?(d.current||(r.addEventListener("message",E),r.addEventListener("open",x),r.addEventListener("close",g),r.addEventListener("connect",C),r.addEventListener("disconnect",h),r.addEventListener("error",M),d.current=!0),()=>{d.current=!1,r.removeEventListener("message",E),r.removeEventListener("open",x),r.removeEventListener("close",g),r.removeEventListener("connect",C),r.removeEventListener("disconnect",h),r.removeEventListener("error",M)}):()=>{},[r,E,x,g,C,h,M])}export{me as MedplumProvider,O as reactContext,Se as useCachedBinaryUrl,b as useMedplum,T as useMedplumContext,se as useMedplumNavigate,ue as useMedplumProfile,xe as usePrevious,Te as useResource,Ne as useSearch,Ue as useSearchOne,We as useSearchResources,Qe as useSubscription};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/MedplumProvider/MedplumProvider.tsx", "../../src/MedplumProvider/MedplumProvider.context.ts", "../../src/useCachedBinaryUrl/useCachedBinaryUrl.ts", "../../src/usePrevious/usePrevious.ts", "../../src/useResource/useResource.ts", "../../src/useSearch/useSearch.ts", "../../src/useSubscription/useSubscription.ts"],
|
|
4
|
-
"sourcesContent": ["import { MedplumClient, MedplumClientEventMap } from '@medplum/core';\nimport { ReactNode, useEffect, useMemo, useState } from 'react';\nimport { MedplumNavigateFunction, reactContext } from './MedplumProvider.context';\n\nexport interface MedplumProviderProps {\n readonly medplum: MedplumClient;\n readonly navigate?: MedplumNavigateFunction;\n readonly children: ReactNode;\n}\n\nconst EVENTS_TO_TRACK = [\n 'change',\n 'storageInitialized',\n 'storageInitFailed',\n 'profileRefreshing',\n 'profileRefreshed',\n] satisfies (keyof MedplumClientEventMap)[];\n\n/**\n * The MedplumProvider component provides Medplum context state.\n *\n * Medplum context includes:\n * 1) medplum - Medplum client library\n * 2) profile - The current user profile (if signed in)\n * @param props - The MedplumProvider React props.\n * @returns The MedplumProvider React node.\n */\nexport function MedplumProvider(props: MedplumProviderProps): JSX.Element {\n const medplum = props.medplum;\n const navigate = props.navigate ?? defaultNavigate;\n\n const [state, setState] = useState({\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n });\n\n useEffect(() => {\n function eventListener(): void {\n setState((s) => ({\n ...s,\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n }));\n }\n\n for (const event of EVENTS_TO_TRACK) {\n medplum.addEventListener(event, eventListener);\n }\n return () => {\n for (const event of EVENTS_TO_TRACK) {\n medplum.removeEventListener(event, eventListener);\n }\n };\n }, [medplum]);\n\n const medplumContext = useMemo(\n () => ({\n ...state,\n medplum,\n navigate,\n }),\n [state, medplum, navigate]\n );\n\n return <reactContext.Provider value={medplumContext}>{props.children}</reactContext.Provider>;\n}\n\n/**\n * The default \"navigate\" function which simply uses window.location.href.\n * @param path - The path to navigate to.\n */\nfunction defaultNavigate(path: string): void {\n window.location.assign(path);\n}\n", "import { MedplumClient, ProfileResource } from '@medplum/core';\nimport { createContext, useContext } from 'react';\n\nexport const reactContext = createContext(undefined as MedplumContext | undefined);\n\nexport type MedplumNavigateFunction = (path: string) => void;\n\nexport interface MedplumContext {\n medplum: MedplumClient;\n navigate: MedplumNavigateFunction;\n profile?: ProfileResource;\n loading: boolean;\n}\n\n/**\n * Returns the MedplumContext instance.\n * @returns The MedplumContext instance.\n */\nexport function useMedplumContext(): MedplumContext {\n return useContext(reactContext) as MedplumContext;\n}\n\n/**\n * Returns the MedplumClient instance.\n * This is a shortcut for useMedplumContext().medplum.\n * @returns The MedplumClient instance.\n */\nexport function useMedplum(): MedplumClient {\n return useMedplumContext().medplum;\n}\n\n/**\n * Returns the Medplum navigate function.\n * @returns The Medplum navigate function.\n */\nexport function useMedplumNavigate(): MedplumNavigateFunction {\n return useMedplumContext().navigate;\n}\n\n/**\n * Returns the current Medplum user profile (if signed in).\n * This is a shortcut for useMedplumContext().profile.\n * @returns The current user profile.\n */\nexport function useMedplumProfile(): ProfileResource | undefined {\n return useMedplumContext().profile;\n}\n", "import { useMemo } from 'react';\n\n// Maintain a cache of urls to avoid unnecessary re-download of attachments\n// The following is a workaround for the fact that each request to a resource containing a Binary data reference\n// returns a NEW signed S3 URL for each bypassing the native browser caching mechanism\n// resulting in unnecessary bandwidth consumption.\n// https://www.medplum.com/docs/fhir-datastore/binary-data#consuming-a-fhir-binary-in-an-application\n// https://github.com/medplum/medplum/issues/3815\n\n// The S3 presigned URLs expire after 1 hour with the default configuration and hard refreshes are not uncommon even in SPAs so this\n// could be a good way to get additional cache hits\n// This would require additional logic for initialization, saving, and purging of expired keys\nconst urls = new Map<string, string>();\n\nexport const useCachedBinaryUrl = (binaryUrl: string | undefined): string | undefined => {\n return useMemo(() => {\n if (!binaryUrl) {\n return undefined;\n }\n\n const binaryResourceUrl = binaryUrl.split('?')[0];\n if (!binaryResourceUrl) {\n return binaryUrl;\n }\n\n // Check if the binaryUrl is a presigned S3 URL\n let binaryUrlSearchParams: URLSearchParams;\n try {\n binaryUrlSearchParams = new URLSearchParams(new URL(binaryUrl).search);\n } catch (_err) {\n return binaryUrl;\n }\n\n if (!binaryUrlSearchParams.has('Key-Pair-Id') || !binaryUrlSearchParams.has('Signature')) {\n return binaryUrl;\n }\n\n // https://stackoverflow.com/questions/23929145/how-to-test-if-a-given-time-stamp-is-in-seconds-or-milliseconds\n const binaryUrlExpires = binaryUrlSearchParams.get('Expires');\n if (!binaryUrlExpires || binaryUrlExpires.length > 13) {\n // Expires is expected to be in seconds, not milliseconds\n return binaryUrl;\n }\n\n const cachedUrl = urls.get(binaryResourceUrl);\n if (cachedUrl) {\n const searchParams = new URLSearchParams(new URL(cachedUrl).search);\n\n // This is fairly brittle as it relies on the current structure of the Medplum returned URL\n const expires = searchParams.get('Expires');\n\n // `expires` is in seconds, Date.now() is in ms\n // Add padding to mitigate expiration between time of check and time of use\n if (expires && parseInt(expires, 10) * 1000 - 5_000 > Date.now()) {\n return cachedUrl;\n }\n }\n\n urls.set(binaryResourceUrl, binaryUrl);\n return binaryUrl;\n }, [binaryUrl]);\n};\n", "import { useEffect, useRef } from 'react';\n\n/**\n * React Hook to keep track of the passed-in value from the previous render of the containing component.\n * @param value - The value to track.\n * @returns The value passed in from the previous render.\n */\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>();\n useEffect(() => {\n ref.current = value;\n });\n return ref.current;\n}\n", "import { deepEquals, isReference, isResource, MedplumClient, normalizeOperationOutcome } from '@medplum/core';\nimport { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * React Hook to use a FHIR reference.\n * Handles the complexity of resolving references and caching resources.\n * @param value - The resource or reference to resource.\n * @param setOutcome - Optional callback to set the OperationOutcome.\n * @returns The resolved resource.\n */\nexport function useResource<T extends Resource>(\n value: Reference<T> | Partial<T> | undefined,\n setOutcome?: (outcome: OperationOutcome) => void\n): T | undefined {\n const medplum = useMedplum();\n const [resource, setResource] = useState<T | undefined>(() => {\n return getInitialResource(medplum, value);\n });\n\n const setResourceIfChanged = useCallback(\n (r: T | undefined) => {\n if (!deepEquals(r, resource)) {\n setResource(r);\n }\n },\n [resource]\n );\n\n useEffect(() => {\n let subscribed = true;\n\n const newValue = getInitialResource(medplum, value);\n if (!newValue && isReference(value)) {\n medplum\n .readReference(value as Reference<T>)\n .then((r) => {\n if (subscribed) {\n setResourceIfChanged(r);\n }\n })\n .catch((err) => {\n if (subscribed) {\n setResourceIfChanged(undefined);\n if (setOutcome) {\n setOutcome(normalizeOperationOutcome(err));\n }\n }\n });\n } else {\n setResourceIfChanged(newValue);\n }\n\n return (() => (subscribed = false)) as () => void;\n }, [medplum, value, setResourceIfChanged, setOutcome]);\n\n return resource;\n}\n\n/**\n * Returns the initial resource value based on the input value.\n * If the input value is a resource, returns the resource.\n * If the input value is a reference to a resource available in the cache, returns the resource.\n * Otherwise, returns undefined.\n * @param medplum - The medplum client.\n * @param value - The resource or reference to resource.\n * @returns An initial resource if available; undefined otherwise.\n */\nfunction getInitialResource<T extends Resource>(\n medplum: MedplumClient,\n value: Reference<T> | Partial<T> | undefined\n): T | undefined {\n if (value) {\n if (isResource(value)) {\n return value as T;\n }\n\n if (isReference(value)) {\n return medplum.getCachedReference(value as Reference<T>);\n }\n }\n\n return undefined;\n}\n", "import { allOk, normalizeOperationOutcome, QueryTypes, ResourceArray } from '@medplum/core';\nimport { Bundle, ExtractResource, OperationOutcome, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\n\n/**\n * React hook for searching FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.search() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearch<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query);\n}\n\n/**\n * React hook for searching for a single FHIR resource.\n *\n * This is a convenience hook for calling the MedplumClient.searchOne() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchOne<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query);\n}\n\n/**\n * React hook for searching for an array of FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.searchResources() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchResources<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query);\n}\n\nfunction useSearchImpl<K extends ResourceType, ReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined\n): [ReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [searchKey, setSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(true);\n const [result, setResult] = useState<ReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n useEffect(() => {\n const key = medplum.fhirSearchUrl(resourceType, query).toString();\n if (key !== searchKey) {\n setSearchKey(key);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as ReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, searchKey]);\n\n return [result, loading, outcome];\n}\n", "import { SubscriptionEmitter, SubscriptionEventMap, deepEquals } from '@medplum/core';\nimport { Bundle, Subscription } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nconst SUBSCRIPTION_DEBOUNCE_MS = 3000;\n\nexport type UseSubscriptionOptions = {\n subscriptionProps?: Partial<Subscription>;\n onWebSocketOpen?: () => void;\n onWebSocketClose?: () => void;\n onSubscriptionConnect?: (subscriptionId: string) => void;\n onSubscriptionDisconnect?: (subscriptionId: string) => void;\n onError?: (err: Error) => void;\n};\n\n/**\n * Creates an in-memory `Subscription` resource with the given criteria on the Medplum server and calls the given callback when an event notification is triggered by a resource interaction over a WebSocket connection.\n *\n * Subscriptions created with this hook are lightweight, share a single WebSocket connection, and are automatically untracked and cleaned up when the containing component is no longer mounted.\n *\n * @param criteria - The FHIR search criteria to subscribe to.\n * @param callback - The callback to call when a notification event `Bundle` for this `Subscription` is received.\n * @param options - Optional options used to configure the created `Subscription`. See {@link UseSubscriptionOptions}\n *\n * --------------------------------------------------------------------------------------------------------------------------------\n *\n * `options` contains the following properties, all of which are optional:\n * - `subscriptionProps` - Allows the caller to pass a `Partial<Subscription>` to use as part of the creation\n * of the `Subscription` resource for this subscription. It enables the user namely to pass things like the `extension` property and to create\n * the `Subscription` with extensions such the {@link https://www.medplum.com/docs/subscriptions/subscription-extensions#interactions \"Supported Interaction\"} extension which would enable to listen for `create` or `update` only events.\n * - `onWebsocketOpen` - Called when the WebSocket connection is established with Medplum server.\n * - `onWebsocketClose` - Called when the WebSocket connection disconnects.\n * - `onSubscriptionConnect` - Called when the corresponding subscription starts to receive updates after the subscription has been initialized and connected to.\n * - `onSubscriptionDisconnect` - Called when the corresponding subscription is destroyed and stops receiving updates from the server.\n * - `onError` - Called whenever an error occurs during the lifecycle of the managed subscription.\n */\nexport function useSubscription(\n criteria: string,\n callback: (bundle: Bundle) => void,\n options?: UseSubscriptionOptions\n): void {\n const medplum = useMedplum();\n const [emitter, setEmitter] = useState<SubscriptionEmitter>();\n // We don't memoize the entire options object since it contains callbacks and if the callbacks change identity, we don't want to trigger a resubscribe to criteria\n const [memoizedSubProps, setMemoizedSubProps] = useState(options?.subscriptionProps);\n\n const listeningRef = useRef(false);\n const unsubTimerRef = useRef<ReturnType<typeof setTimeout>>();\n\n const prevCriteriaRef = useRef<string>();\n const prevMemoizedSubPropsRef = useRef<UseSubscriptionOptions['subscriptionProps']>();\n\n const callbackRef = useRef<typeof callback>();\n callbackRef.current = callback;\n\n const onWebSocketOpenRef = useRef<UseSubscriptionOptions['onWebSocketOpen']>();\n onWebSocketOpenRef.current = options?.onWebSocketOpen;\n\n const onWebSocketCloseRef = useRef<UseSubscriptionOptions['onWebSocketClose']>();\n onWebSocketCloseRef.current = options?.onWebSocketClose;\n\n const onSubscriptionConnectRef = useRef<UseSubscriptionOptions['onSubscriptionConnect']>();\n onSubscriptionConnectRef.current = options?.onSubscriptionConnect;\n\n const onSubscriptionDisconnectRef = useRef<UseSubscriptionOptions['onSubscriptionDisconnect']>();\n onSubscriptionDisconnectRef.current = options?.onSubscriptionDisconnect;\n\n const onErrorRef = useRef<UseSubscriptionOptions['onError']>();\n onErrorRef.current = options?.onError;\n\n useEffect(() => {\n // Deep equals checks referential equality first\n if (!deepEquals(options?.subscriptionProps, memoizedSubProps)) {\n setMemoizedSubProps(options?.subscriptionProps);\n }\n }, [memoizedSubProps, options]);\n\n useEffect(() => {\n if (unsubTimerRef.current) {\n clearTimeout(unsubTimerRef.current);\n unsubTimerRef.current = undefined;\n }\n\n let shouldSubscribe = false;\n if (prevCriteriaRef.current !== criteria || !deepEquals(prevMemoizedSubPropsRef.current, memoizedSubProps)) {\n shouldSubscribe = true;\n }\n\n if (shouldSubscribe && prevCriteriaRef.current) {\n medplum.unsubscribeFromCriteria(prevCriteriaRef.current, prevMemoizedSubPropsRef.current);\n }\n\n // Set prev criteria and options to latest after checking them\n prevCriteriaRef.current = criteria;\n prevMemoizedSubPropsRef.current = memoizedSubProps;\n\n // We do this after as to not immediately trigger re-render\n if (shouldSubscribe) {\n setEmitter(medplum.subscribeToCriteria(criteria, memoizedSubProps));\n }\n\n return () => {\n unsubTimerRef.current = setTimeout(() => {\n setEmitter(undefined);\n medplum.unsubscribeFromCriteria(criteria, memoizedSubProps);\n }, SUBSCRIPTION_DEBOUNCE_MS);\n };\n }, [medplum, criteria, memoizedSubProps]);\n\n const emitterCallback = useCallback((event: SubscriptionEventMap['message']) => {\n callbackRef.current?.(event.payload);\n }, []);\n\n const onWebSocketOpen = useCallback(() => {\n onWebSocketOpenRef.current?.();\n }, []);\n\n const onWebSocketClose = useCallback(() => {\n onWebSocketCloseRef.current?.();\n }, []);\n\n const onSubscriptionConnect = useCallback((event: SubscriptionEventMap['connect']) => {\n onSubscriptionConnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onSubscriptionDisconnect = useCallback((event: SubscriptionEventMap['disconnect']) => {\n onSubscriptionDisconnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onError = useCallback((event: SubscriptionEventMap['error']) => {\n onErrorRef.current?.(event.payload);\n }, []);\n\n useEffect(() => {\n if (!emitter) {\n return () => undefined;\n }\n if (!listeningRef.current) {\n emitter.addEventListener('message', emitterCallback);\n emitter.addEventListener('open', onWebSocketOpen);\n emitter.addEventListener('close', onWebSocketClose);\n emitter.addEventListener('connect', onSubscriptionConnect);\n emitter.addEventListener('disconnect', onSubscriptionDisconnect);\n emitter.addEventListener('error', onError);\n listeningRef.current = true;\n }\n return () => {\n listeningRef.current = false;\n emitter.removeEventListener('message', emitterCallback);\n emitter.removeEventListener('open', onWebSocketOpen);\n emitter.removeEventListener('close', onWebSocketClose);\n emitter.removeEventListener('connect', onSubscriptionConnect);\n emitter.removeEventListener('disconnect', onSubscriptionDisconnect);\n emitter.removeEventListener('error', onError);\n };\n }, [\n emitter,\n emitterCallback,\n onWebSocketOpen,\n onWebSocketClose,\n onSubscriptionConnect,\n onSubscriptionDisconnect,\n onError,\n ]);\n}\n"],
|
|
5
|
-
"mappings": "AACA,OAAoB,aAAAA,EAAW,WAAAC,EAAS,YAAAC,MAAgB,QCAxD,OAAS,iBAAAC,EAAe,cAAAC,MAAkB,QAEnC,IAAMC,EAAeF,EAAc,MAAuC,EAe1E,SAASG,GAAoC,CAClD,OAAOF,EAAWC,CAAY,CAChC,CAOO,SAASE,GAA4B,CAC1C,OAAOD,EAAkB,EAAE,OAC7B,CAMO,SAASE,IAA8C,CAC5D,OAAOF,EAAkB,EAAE,QAC7B,CAOO,SAASG,IAAiD,CAC/D,OAAOH,EAAkB,EAAE,OAC7B,CDkBS,cAAAI,MAAA,oBAtDT,IAAMC,EAAkB,CACtB,SACA,qBACA,oBACA,oBACA,kBACF,EAWO,SAASC,GAAgBC,EAA0C,CACxE,IAAMC,EAAUD,EAAM,QAChBE,EAAWF,EAAM,UAAYG,EAE7B,CAACC,EAAOC,CAAQ,EAAIC,EAAS,CACjC,QAASL,EAAQ,WAAW,EAC5B,QAASA,EAAQ,UAAU,CAC7B,CAAC,EAEDM,EAAU,IAAM,CACd,SAASC,GAAsB,CAC7BH,EAAUI,IAAO,CACf,GAAGA,EACH,QAASR,EAAQ,WAAW,EAC5B,QAASA,EAAQ,UAAU,CAC7B,EAAE,CACJ,CAEA,QAAWS,KAASZ,EAClBG,EAAQ,iBAAiBS,EAAOF,CAAa,EAE/C,MAAO,IAAM,CACX,QAAWE,KAASZ,EAClBG,EAAQ,oBAAoBS,EAAOF,CAAa,CAEpD,CACF,EAAG,CAACP,CAAO,CAAC,EAEZ,IAAMU,EAAiBC,EACrB,KAAO,CACL,GAAGR,EACH,QAAAH,EACA,SAAAC,CACF,GACA,CAACE,EAAOH,EAASC,CAAQ,CAC3B,EAEA,OAAOL,EAACgB,EAAa,SAAb,CAAsB,MAAOF,EAAiB,SAAAX,EAAM,SAAS,CACvE,CAMA,SAASG,EAAgBW,EAAoB,CAC3C,OAAO,SAAS,OAAOA,CAAI,CAC7B,CEzEA,OAAS,WAAAC,MAAe,QAYxB,IAAMC,EAAO,IAAI,IAEJC,GAAsBC,GAC1BH,EAAQ,IAAM,CACnB,GAAI,CAACG,EACH,OAGF,IAAMC,EAAoBD,EAAU,MAAM,GAAG,EAAE,CAAC,EAChD,GAAI,CAACC,EACH,OAAOD,EAIT,IAAIE,EACJ,GAAI,CACFA,EAAwB,IAAI,gBAAgB,IAAI,IAAIF,CAAS,EAAE,MAAM,CACvE,MAAe,CACb,OAAOA,CACT,CAEA,GAAI,CAACE,EAAsB,IAAI,aAAa,GAAK,CAACA,EAAsB,IAAI,WAAW,EACrF,OAAOF,EAIT,IAAMG,EAAmBD,EAAsB,IAAI,SAAS,EAC5D,GAAI,CAACC,GAAoBA,EAAiB,OAAS,GAEjD,OAAOH,EAGT,IAAMI,EAAYN,EAAK,IAAIG,CAAiB,EAC5C,GAAIG,EAAW,CAIb,IAAMC,EAHe,IAAI,gBAAgB,IAAI,IAAID,CAAS,EAAE,MAAM,EAGrC,IAAI,SAAS,EAI1C,GAAIC,GAAW,SAASA,EAAS,EAAE,EAAI,IAAO,IAAQ,KAAK,IAAI,EAC7D,OAAOD,CAEX,CAEA,OAAAN,EAAK,IAAIG,EAAmBD,CAAS,EAC9BA,CACT,EAAG,CAACA,CAAS,CAAC,EC5DhB,OAAS,aAAAM,EAAW,UAAAC,MAAc,QAO3B,SAASC,GAAeC,EAAyB,CACtD,IAAMC,EAAMH,EAAU,EACtB,OAAAD,EAAU,IAAM,CACdI,EAAI,QAAUD,CAChB,CAAC,EACMC,EAAI,OACb,CCbA,OAAS,cAAAC,EAAY,eAAAC,EAAa,cAAAC,EAA2B,6BAAAC,MAAiC,gBAE9F,OAAS,eAAAC,EAAa,aAAAC,EAAW,YAAAC,OAAgB,QAU1C,SAASC,GACdC,EACAC,EACe,CACf,IAAMC,EAAUC,EAAW,EACrB,CAACC,EAAUC,CAAW,EAAIC,GAAwB,IAC/CC,EAAmBL,EAASF,CAAK,CACzC,EAEKQ,EAAuBC,EAC1BC,GAAqB,CACfC,EAAWD,EAAGN,CAAQ,GACzBC,EAAYK,CAAC,CAEjB,EACA,CAACN,CAAQ,CACX,EAEA,OAAAQ,EAAU,IAAM,CACd,IAAIC,EAAa,GAEXC,EAAWP,EAAmBL,EAASF,CAAK,EAClD,MAAI,CAACc,GAAYC,EAAYf,CAAK,EAChCE,EACG,cAAcF,CAAqB,EACnC,KAAMU,GAAM,CACPG,GACFL,EAAqBE,CAAC,CAE1B,CAAC,EACA,MAAOM,GAAQ,CACVH,IACFL,EAAqB,MAAS,EAC1BP,GACFA,EAAWgB,EAA0BD,CAAG,CAAC,EAG/C,CAAC,EAEHR,EAAqBM,CAAQ,EAGvB,IAAOD,EAAa,EAC9B,EAAG,CAACX,EAASF,EAAOQ,EAAsBP,CAAU,CAAC,EAE9CG,CACT,CAWA,SAASG,EACPL,EACAF,EACe,CACf,GAAIA,EAAO,CACT,GAAIkB,EAAWlB,CAAK,EAClB,OAAOA,EAGT,GAAIe,EAAYf,CAAK,EACnB,OAAOE,EAAQ,mBAAmBF,CAAqB,CAE3D,CAGF,CCpFA,OAAS,SAAAmB,GAAO,6BAAAC,OAA4D,gBAE5E,OAAS,aAAAC,GAAW,YAAAC,MAAgB,QAc7B,SAASC,GACdC,EACAC,EACiF,CACjF,OAAOC,EAA6C,SAAUF,EAAcC,CAAK,CACnF,CAWO,SAASE,GACdH,EACAC,EACyE,CACzE,OAAOC,EAAqC,YAAaF,EAAcC,CAAK,CAC9E,CAWO,SAASG,GACdJ,EACAC,EACwF,CACxF,OAAOC,EAAoD,kBAAmBF,EAAcC,CAAK,CACnG,CAEA,SAASC,EACPG,EACAL,EACAC,EACiE,CACjE,IAAMK,EAAUC,EAAW,EACrB,CAACC,EAAWC,CAAY,EAAIC,EAAiB,EAC7C,CAACC,EAASC,CAAU,EAAIF,EAAkB,EAAI,EAC9C,CAACG,EAAQC,CAAS,EAAIJ,EAAqB,EAC3C,CAACK,EAASC,CAAU,EAAIN,EAA2B,EAEzD,OAAAO,GAAU,IAAM,CACd,IAAMC,EAAMZ,EAAQ,cAAcN,EAAcC,CAAK,EAAE,SAAS,EAC5DiB,IAAQV,IACVC,EAAaS,CAAG,EAChBZ,EAAQD,CAAQ,EAAEL,EAAcC,CAAK,EAClC,KAAMkB,GAAQ,CACbP,EAAW,EAAK,EAChBE,EAAUK,CAAiB,EAC3BH,EAAWI,EAAK,CAClB,CAAC,EACA,MAAOC,GAAQ,CACdT,EAAW,EAAK,EAChBE,EAAU,MAAS,EACnBE,EAAWM,GAA0BD,CAAG,CAAC,CAC3C,CAAC,EAEP,EAAG,CAACf,EAASD,EAAUL,EAAcC,EAAOO,CAAS,CAAC,EAE/C,CAACK,EAAQF,EAASI,CAAO,CAClC,CCrFA,OAAoD,cAAAQ,MAAkB,gBAEtE,OAAS,eAAAC,EAAa,aAAAC,EAAW,UAAAC,EAAQ,YAAAC,MAAgB,QAGzD,IAAMC,GAA2B,IAgC1B,SAASC,GACdC,EACAC,EACAC,EACM,CACN,IAAMC,EAAUC,EAAW,EACrB,CAACC,EAASC,CAAU,EAAIC,EAA8B,EAEtD,CAACC,EAAkBC,CAAmB,EAAIF,EAASL,GAAS,iBAAiB,EAE7EQ,EAAeC,EAAO,EAAK,EAC3BC,EAAgBD,EAAsC,EAEtDE,EAAkBF,
|
|
4
|
+
"sourcesContent": ["import { MedplumClient, MedplumClientEventMap } from '@medplum/core';\nimport { ReactNode, useEffect, useMemo, useState } from 'react';\nimport { MedplumNavigateFunction, reactContext } from './MedplumProvider.context';\n\nexport interface MedplumProviderProps {\n readonly medplum: MedplumClient;\n readonly navigate?: MedplumNavigateFunction;\n readonly children: ReactNode;\n}\n\nconst EVENTS_TO_TRACK = [\n 'change',\n 'storageInitialized',\n 'storageInitFailed',\n 'profileRefreshing',\n 'profileRefreshed',\n] satisfies (keyof MedplumClientEventMap)[];\n\n/**\n * The MedplumProvider component provides Medplum context state.\n *\n * Medplum context includes:\n * 1) medplum - Medplum client library\n * 2) profile - The current user profile (if signed in)\n * @param props - The MedplumProvider React props.\n * @returns The MedplumProvider React node.\n */\nexport function MedplumProvider(props: MedplumProviderProps): JSX.Element {\n const medplum = props.medplum;\n const navigate = props.navigate ?? defaultNavigate;\n\n const [state, setState] = useState({\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n });\n\n useEffect(() => {\n function eventListener(): void {\n setState((s) => ({\n ...s,\n profile: medplum.getProfile(),\n loading: medplum.isLoading(),\n }));\n }\n\n for (const event of EVENTS_TO_TRACK) {\n medplum.addEventListener(event, eventListener);\n }\n return () => {\n for (const event of EVENTS_TO_TRACK) {\n medplum.removeEventListener(event, eventListener);\n }\n };\n }, [medplum]);\n\n const medplumContext = useMemo(\n () => ({\n ...state,\n medplum,\n navigate,\n }),\n [state, medplum, navigate]\n );\n\n return <reactContext.Provider value={medplumContext}>{props.children}</reactContext.Provider>;\n}\n\n/**\n * The default \"navigate\" function which simply uses window.location.href.\n * @param path - The path to navigate to.\n */\nfunction defaultNavigate(path: string): void {\n window.location.assign(path);\n}\n", "import { MedplumClient, ProfileResource } from '@medplum/core';\nimport { createContext, useContext } from 'react';\n\nexport const reactContext = createContext(undefined as MedplumContext | undefined);\n\nexport type MedplumNavigateFunction = (path: string) => void;\n\nexport interface MedplumContext {\n medplum: MedplumClient;\n navigate: MedplumNavigateFunction;\n profile?: ProfileResource;\n loading: boolean;\n}\n\n/**\n * Returns the MedplumContext instance.\n * @returns The MedplumContext instance.\n */\nexport function useMedplumContext(): MedplumContext {\n return useContext(reactContext) as MedplumContext;\n}\n\n/**\n * Returns the MedplumClient instance.\n * This is a shortcut for useMedplumContext().medplum.\n * @returns The MedplumClient instance.\n */\nexport function useMedplum(): MedplumClient {\n return useMedplumContext().medplum;\n}\n\n/**\n * Returns the Medplum navigate function.\n * @returns The Medplum navigate function.\n */\nexport function useMedplumNavigate(): MedplumNavigateFunction {\n return useMedplumContext().navigate;\n}\n\n/**\n * Returns the current Medplum user profile (if signed in).\n * This is a shortcut for useMedplumContext().profile.\n * @returns The current user profile.\n */\nexport function useMedplumProfile(): ProfileResource | undefined {\n return useMedplumContext().profile;\n}\n", "import { useMemo } from 'react';\n\n// Maintain a cache of urls to avoid unnecessary re-download of attachments\n// The following is a workaround for the fact that each request to a resource containing a Binary data reference\n// returns a NEW signed S3 URL for each bypassing the native browser caching mechanism\n// resulting in unnecessary bandwidth consumption.\n// https://www.medplum.com/docs/fhir-datastore/binary-data#consuming-a-fhir-binary-in-an-application\n// https://github.com/medplum/medplum/issues/3815\n\n// The S3 presigned URLs expire after 1 hour with the default configuration and hard refreshes are not uncommon even in SPAs so this\n// could be a good way to get additional cache hits\n// This would require additional logic for initialization, saving, and purging of expired keys\nconst urls = new Map<string, string>();\n\nexport const useCachedBinaryUrl = (binaryUrl: string | undefined): string | undefined => {\n return useMemo(() => {\n if (!binaryUrl) {\n return undefined;\n }\n\n const binaryResourceUrl = binaryUrl.split('?')[0];\n if (!binaryResourceUrl) {\n return binaryUrl;\n }\n\n // Check if the binaryUrl is a presigned S3 URL\n let binaryUrlSearchParams: URLSearchParams;\n try {\n binaryUrlSearchParams = new URLSearchParams(new URL(binaryUrl).search);\n } catch (_err) {\n return binaryUrl;\n }\n\n if (!binaryUrlSearchParams.has('Key-Pair-Id') || !binaryUrlSearchParams.has('Signature')) {\n return binaryUrl;\n }\n\n // https://stackoverflow.com/questions/23929145/how-to-test-if-a-given-time-stamp-is-in-seconds-or-milliseconds\n const binaryUrlExpires = binaryUrlSearchParams.get('Expires');\n if (!binaryUrlExpires || binaryUrlExpires.length > 13) {\n // Expires is expected to be in seconds, not milliseconds\n return binaryUrl;\n }\n\n const cachedUrl = urls.get(binaryResourceUrl);\n if (cachedUrl) {\n const searchParams = new URLSearchParams(new URL(cachedUrl).search);\n\n // This is fairly brittle as it relies on the current structure of the Medplum returned URL\n const expires = searchParams.get('Expires');\n\n // `expires` is in seconds, Date.now() is in ms\n // Add padding to mitigate expiration between time of check and time of use\n if (expires && parseInt(expires, 10) * 1000 - 5_000 > Date.now()) {\n return cachedUrl;\n }\n }\n\n urls.set(binaryResourceUrl, binaryUrl);\n return binaryUrl;\n }, [binaryUrl]);\n};\n", "import { useEffect, useRef } from 'react';\n\n/**\n * React Hook to keep track of the passed-in value from the previous render of the containing component.\n * @param value - The value to track.\n * @returns The value passed in from the previous render.\n */\nexport function usePrevious<T>(value: T): T | undefined {\n const ref = useRef<T>();\n useEffect(() => {\n ref.current = value;\n });\n return ref.current;\n}\n", "import { deepEquals, isReference, isResource, MedplumClient, normalizeOperationOutcome } from '@medplum/core';\nimport { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * React Hook to use a FHIR reference.\n * Handles the complexity of resolving references and caching resources.\n * @param value - The resource or reference to resource.\n * @param setOutcome - Optional callback to set the OperationOutcome.\n * @returns The resolved resource.\n */\nexport function useResource<T extends Resource>(\n value: Reference<T> | Partial<T> | undefined,\n setOutcome?: (outcome: OperationOutcome) => void\n): T | undefined {\n const medplum = useMedplum();\n const [resource, setResource] = useState<T | undefined>(() => {\n return getInitialResource(medplum, value);\n });\n\n const setResourceIfChanged = useCallback(\n (r: T | undefined) => {\n if (!deepEquals(r, resource)) {\n setResource(r);\n }\n },\n [resource]\n );\n\n useEffect(() => {\n let subscribed = true;\n\n const newValue = getInitialResource(medplum, value);\n if (!newValue && isReference(value)) {\n medplum\n .readReference(value as Reference<T>)\n .then((r) => {\n if (subscribed) {\n setResourceIfChanged(r);\n }\n })\n .catch((err) => {\n if (subscribed) {\n setResourceIfChanged(undefined);\n if (setOutcome) {\n setOutcome(normalizeOperationOutcome(err));\n }\n }\n });\n } else {\n setResourceIfChanged(newValue);\n }\n\n return (() => (subscribed = false)) as () => void;\n }, [medplum, value, setResourceIfChanged, setOutcome]);\n\n return resource;\n}\n\n/**\n * Returns the initial resource value based on the input value.\n * If the input value is a resource, returns the resource.\n * If the input value is a reference to a resource available in the cache, returns the resource.\n * Otherwise, returns undefined.\n * @param medplum - The medplum client.\n * @param value - The resource or reference to resource.\n * @returns An initial resource if available; undefined otherwise.\n */\nfunction getInitialResource<T extends Resource>(\n medplum: MedplumClient,\n value: Reference<T> | Partial<T> | undefined\n): T | undefined {\n if (value) {\n if (isResource(value)) {\n return value as T;\n }\n\n if (isReference(value)) {\n return medplum.getCachedReference(value as Reference<T>);\n }\n }\n\n return undefined;\n}\n", "import { allOk, normalizeOperationOutcome, QueryTypes, ResourceArray } from '@medplum/core';\nimport { Bundle, ExtractResource, OperationOutcome, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\n\n/**\n * React hook for searching FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.search() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearch<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query);\n}\n\n/**\n * React hook for searching for a single FHIR resource.\n *\n * This is a convenience hook for calling the MedplumClient.searchOne() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchOne<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query);\n}\n\n/**\n * React hook for searching for an array of FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.searchResources() method.\n *\n * @param resourceType - The FHIR resource type to search.\n * @param query - Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchResources<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query);\n}\n\nfunction useSearchImpl<K extends ResourceType, ReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined\n): [ReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [searchKey, setSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(true);\n const [result, setResult] = useState<ReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n useEffect(() => {\n const key = medplum.fhirSearchUrl(resourceType, query).toString();\n if (key !== searchKey) {\n setSearchKey(key);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as ReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, searchKey]);\n\n return [result, loading, outcome];\n}\n", "import { SubscriptionEmitter, SubscriptionEventMap, deepEquals } from '@medplum/core';\nimport { Bundle, Subscription } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\nconst SUBSCRIPTION_DEBOUNCE_MS = 3000;\n\nexport type UseSubscriptionOptions = {\n subscriptionProps?: Partial<Subscription>;\n onWebSocketOpen?: () => void;\n onWebSocketClose?: () => void;\n onSubscriptionConnect?: (subscriptionId: string) => void;\n onSubscriptionDisconnect?: (subscriptionId: string) => void;\n onError?: (err: Error) => void;\n};\n\n/**\n * Creates an in-memory `Subscription` resource with the given criteria on the Medplum server and calls the given callback when an event notification is triggered by a resource interaction over a WebSocket connection.\n *\n * Subscriptions created with this hook are lightweight, share a single WebSocket connection, and are automatically untracked and cleaned up when the containing component is no longer mounted.\n *\n * @param criteria - The FHIR search criteria to subscribe to.\n * @param callback - The callback to call when a notification event `Bundle` for this `Subscription` is received.\n * @param options - Optional options used to configure the created `Subscription`. See {@link UseSubscriptionOptions}\n *\n * --------------------------------------------------------------------------------------------------------------------------------\n *\n * `options` contains the following properties, all of which are optional:\n * - `subscriptionProps` - Allows the caller to pass a `Partial<Subscription>` to use as part of the creation\n * of the `Subscription` resource for this subscription. It enables the user namely to pass things like the `extension` property and to create\n * the `Subscription` with extensions such the {@link https://www.medplum.com/docs/subscriptions/subscription-extensions#interactions \"Supported Interaction\"} extension which would enable to listen for `create` or `update` only events.\n * - `onWebsocketOpen` - Called when the WebSocket connection is established with Medplum server.\n * - `onWebsocketClose` - Called when the WebSocket connection disconnects.\n * - `onSubscriptionConnect` - Called when the corresponding subscription starts to receive updates after the subscription has been initialized and connected to.\n * - `onSubscriptionDisconnect` - Called when the corresponding subscription is destroyed and stops receiving updates from the server.\n * - `onError` - Called whenever an error occurs during the lifecycle of the managed subscription.\n */\nexport function useSubscription(\n criteria: string | undefined,\n callback: (bundle: Bundle) => void,\n options?: UseSubscriptionOptions\n): void {\n const medplum = useMedplum();\n const [emitter, setEmitter] = useState<SubscriptionEmitter>();\n // We don't memoize the entire options object since it contains callbacks and if the callbacks change identity, we don't want to trigger a resubscribe to criteria\n const [memoizedSubProps, setMemoizedSubProps] = useState(options?.subscriptionProps);\n\n const listeningRef = useRef(false);\n const unsubTimerRef = useRef<ReturnType<typeof setTimeout>>();\n\n const prevCriteriaRef = useRef<string | undefined>();\n const prevMemoizedSubPropsRef = useRef<UseSubscriptionOptions['subscriptionProps']>();\n\n const callbackRef = useRef<typeof callback>();\n callbackRef.current = callback;\n\n const onWebSocketOpenRef = useRef<UseSubscriptionOptions['onWebSocketOpen']>();\n onWebSocketOpenRef.current = options?.onWebSocketOpen;\n\n const onWebSocketCloseRef = useRef<UseSubscriptionOptions['onWebSocketClose']>();\n onWebSocketCloseRef.current = options?.onWebSocketClose;\n\n const onSubscriptionConnectRef = useRef<UseSubscriptionOptions['onSubscriptionConnect']>();\n onSubscriptionConnectRef.current = options?.onSubscriptionConnect;\n\n const onSubscriptionDisconnectRef = useRef<UseSubscriptionOptions['onSubscriptionDisconnect']>();\n onSubscriptionDisconnectRef.current = options?.onSubscriptionDisconnect;\n\n const onErrorRef = useRef<UseSubscriptionOptions['onError']>();\n onErrorRef.current = options?.onError;\n\n useEffect(() => {\n // Deep equals checks referential equality first\n if (!deepEquals(options?.subscriptionProps, memoizedSubProps)) {\n setMemoizedSubProps(options?.subscriptionProps);\n }\n }, [memoizedSubProps, options]);\n\n useEffect(() => {\n if (unsubTimerRef.current) {\n clearTimeout(unsubTimerRef.current);\n unsubTimerRef.current = undefined;\n }\n\n let shouldSubscribe = false;\n if (prevCriteriaRef.current !== criteria || !deepEquals(prevMemoizedSubPropsRef.current, memoizedSubProps)) {\n shouldSubscribe = true;\n }\n\n if (shouldSubscribe && prevCriteriaRef.current) {\n medplum.unsubscribeFromCriteria(prevCriteriaRef.current, prevMemoizedSubPropsRef.current);\n }\n\n // Set prev criteria and options to latest after checking them\n prevCriteriaRef.current = criteria;\n prevMemoizedSubPropsRef.current = memoizedSubProps;\n\n // We do this after as to not immediately trigger re-render\n if (shouldSubscribe && criteria) {\n setEmitter(medplum.subscribeToCriteria(criteria, memoizedSubProps));\n } else if (!criteria) {\n setEmitter(undefined);\n }\n\n return () => {\n unsubTimerRef.current = setTimeout(() => {\n setEmitter(undefined);\n if (criteria) {\n medplum.unsubscribeFromCriteria(criteria, memoizedSubProps);\n }\n }, SUBSCRIPTION_DEBOUNCE_MS);\n };\n }, [medplum, criteria, memoizedSubProps]);\n\n const emitterCallback = useCallback((event: SubscriptionEventMap['message']) => {\n callbackRef.current?.(event.payload);\n }, []);\n\n const onWebSocketOpen = useCallback(() => {\n onWebSocketOpenRef.current?.();\n }, []);\n\n const onWebSocketClose = useCallback(() => {\n onWebSocketCloseRef.current?.();\n }, []);\n\n const onSubscriptionConnect = useCallback((event: SubscriptionEventMap['connect']) => {\n onSubscriptionConnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onSubscriptionDisconnect = useCallback((event: SubscriptionEventMap['disconnect']) => {\n onSubscriptionDisconnectRef.current?.(event.payload.subscriptionId);\n }, []);\n\n const onError = useCallback((event: SubscriptionEventMap['error']) => {\n onErrorRef.current?.(event.payload);\n }, []);\n\n useEffect(() => {\n if (!emitter) {\n return () => undefined;\n }\n if (!listeningRef.current) {\n emitter.addEventListener('message', emitterCallback);\n emitter.addEventListener('open', onWebSocketOpen);\n emitter.addEventListener('close', onWebSocketClose);\n emitter.addEventListener('connect', onSubscriptionConnect);\n emitter.addEventListener('disconnect', onSubscriptionDisconnect);\n emitter.addEventListener('error', onError);\n listeningRef.current = true;\n }\n return () => {\n listeningRef.current = false;\n emitter.removeEventListener('message', emitterCallback);\n emitter.removeEventListener('open', onWebSocketOpen);\n emitter.removeEventListener('close', onWebSocketClose);\n emitter.removeEventListener('connect', onSubscriptionConnect);\n emitter.removeEventListener('disconnect', onSubscriptionDisconnect);\n emitter.removeEventListener('error', onError);\n };\n }, [\n emitter,\n emitterCallback,\n onWebSocketOpen,\n onWebSocketClose,\n onSubscriptionConnect,\n onSubscriptionDisconnect,\n onError,\n ]);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["useEffect", "useMemo", "useState", "createContext", "useContext", "reactContext", "useMedplumContext", "useMedplum", "useMedplumNavigate", "useMedplumProfile", "jsx", "EVENTS_TO_TRACK", "MedplumProvider", "props", "medplum", "navigate", "defaultNavigate", "state", "setState", "useState", "useEffect", "eventListener", "s", "event", "medplumContext", "useMemo", "reactContext", "path", "useMemo", "urls", "useCachedBinaryUrl", "binaryUrl", "binaryResourceUrl", "binaryUrlSearchParams", "binaryUrlExpires", "cachedUrl", "expires", "useEffect", "useRef", "usePrevious", "value", "ref", "deepEquals", "isReference", "isResource", "normalizeOperationOutcome", "useCallback", "useEffect", "useState", "useResource", "value", "setOutcome", "medplum", "useMedplum", "resource", "setResource", "useState", "getInitialResource", "setResourceIfChanged", "useCallback", "r", "deepEquals", "useEffect", "subscribed", "newValue", "isReference", "err", "normalizeOperationOutcome", "isResource", "allOk", "normalizeOperationOutcome", "useEffect", "useState", "useSearch", "resourceType", "query", "useSearchImpl", "useSearchOne", "useSearchResources", "searchFn", "medplum", "useMedplum", "searchKey", "setSearchKey", "useState", "loading", "setLoading", "result", "setResult", "outcome", "setOutcome", "useEffect", "key", "res", "allOk", "err", "normalizeOperationOutcome", "deepEquals", "useCallback", "useEffect", "useRef", "useState", "SUBSCRIPTION_DEBOUNCE_MS", "useSubscription", "criteria", "callback", "options", "medplum", "useMedplum", "emitter", "setEmitter", "useState", "memoizedSubProps", "setMemoizedSubProps", "listeningRef", "useRef", "unsubTimerRef", "prevCriteriaRef", "prevMemoizedSubPropsRef", "callbackRef", "onWebSocketOpenRef", "onWebSocketCloseRef", "onSubscriptionConnectRef", "onSubscriptionDisconnectRef", "onErrorRef", "useEffect", "deepEquals", "shouldSubscribe", "emitterCallback", "useCallback", "event", "onWebSocketOpen", "onWebSocketClose", "onSubscriptionConnect", "onSubscriptionDisconnect", "onError"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medplum/react-hooks",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.12",
|
|
4
4
|
"description": "Medplum React Hooks Library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"medplum",
|
|
@@ -58,16 +58,16 @@
|
|
|
58
58
|
"test": "jest"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@medplum/core": "3.2.
|
|
62
|
-
"@medplum/definitions": "3.2.
|
|
63
|
-
"@medplum/fhirtypes": "3.2.
|
|
64
|
-
"@medplum/mock": "3.2.
|
|
61
|
+
"@medplum/core": "3.2.12",
|
|
62
|
+
"@medplum/definitions": "3.2.12",
|
|
63
|
+
"@medplum/fhirtypes": "3.2.12",
|
|
64
|
+
"@medplum/mock": "3.2.12",
|
|
65
65
|
"@testing-library/dom": "10.4.0",
|
|
66
66
|
"@testing-library/jest-dom": "6.5.0",
|
|
67
67
|
"@testing-library/react": "16.0.1",
|
|
68
|
-
"@types/jest": "29.5.
|
|
69
|
-
"@types/node": "22.5.
|
|
70
|
-
"@types/react": "18.3.
|
|
68
|
+
"@types/jest": "29.5.13",
|
|
69
|
+
"@types/node": "22.5.5",
|
|
70
|
+
"@types/react": "18.3.6",
|
|
71
71
|
"@types/react-dom": "18.3.0",
|
|
72
72
|
"jest": "29.7.0",
|
|
73
73
|
"jest-each": "29.7.0",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"typescript": "5.6.2"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@medplum/core": "3.2.
|
|
81
|
+
"@medplum/core": "3.2.12",
|
|
82
82
|
"react": "^17.0.2 || ^18.0.0",
|
|
83
83
|
"react-dom": "^17.0.2 || ^18.0.0"
|
|
84
84
|
},
|