@medplum/react-hooks 4.1.3 → 4.1.5
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 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +2 -2
- package/package.json +11 -11
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var N=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var H=(e,n)=>{for(var t in n)N(e,t,{get:n[t],enumerable:!0})},Y=(e,n,t,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of q(n))!G.call(e,r)&&r!==t&&N(e,r,{get:()=>n[r],enumerable:!(i=X(n,r))||i.enumerable});return e};var Z=e=>Y(N({},"__esModule",{value:!0}),e);var pe={};H(pe,{MedplumProvider:()=>ee,reactContext:()=>
|
|
1
|
+
"use strict";var N=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var H=(e,n)=>{for(var t in n)N(e,t,{get:n[t],enumerable:!0})},Y=(e,n,t,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of q(n))!G.call(e,r)&&r!==t&&N(e,r,{get:()=>n[r],enumerable:!(i=X(n,r))||i.enumerable});return e};var Z=e=>Y(N({},"__esModule",{value:!0}),e);var pe={};H(pe,{MedplumProvider:()=>ee,reactContext:()=>T,useCachedBinaryUrl:()=>te,useMedplum:()=>l,useMedplumContext:()=>M,useMedplumNavigate:()=>$,useMedplumProfile:()=>j,usePrevious:()=>re,useResource:()=>oe,useSearch:()=>ie,useSearchOne:()=>ue,useSearchResources:()=>ce,useSubscription:()=>ae});module.exports=Z(pe);var g=require("react");var C=require("react"),T=(0,C.createContext)(void 0);function M(){return(0,C.useContext)(T)}function l(){return M().medplum}function $(){return M().navigate}function j(){return M().profile}var _=require("react/jsx-runtime"),V=["change","storageInitialized","storageInitFailed","profileRefreshing","profileRefreshed"];function ee(e){let n=e.medplum,t=e.navigate??ne,[i,r]=(0,g.useState)({profile:n.getProfile(),loading:n.isLoading()});(0,g.useEffect)(()=>{function o(){r(c=>({...c,profile:n.getProfile(),loading:n.isLoading()}))}for(let c of V)n.addEventListener(c,o);return()=>{for(let c of V)n.removeEventListener(c,o)}},[n]);let u=(0,g.useMemo)(()=>({...i,medplum:n,navigate:t}),[i,n,t]);return(0,_.jsx)(T.Provider,{value:u,children:e.children})}function ne(e){window.location.assign(e)}var A=require("react"),z=new Map,te=e=>(0,A.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 i=t.get("Expires");if(!i||i.length>13)return e;let r=z.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 z.set(n,e),e},[e]);var y=require("react");function re(e){let n=(0,y.useRef)(void 0);return(0,y.useEffect)(()=>{n.current=e}),n.current}var f=require("@medplum/core"),v=require("react");function oe(e,n){let t=l(),[i,r]=(0,v.useState)(()=>Q(t,e)),u=(0,v.useCallback)(o=>{(0,f.deepEquals)(o,i)||r(o)},[i]);return(0,v.useEffect)(()=>{let o=!0,c=Q(t,e);return!c&&(0,f.isReference)(e)?t.readReference(e).then(d=>{o&&u(d)}).catch(d=>{o&&(u(void 0),n&&n((0,f.normalizeOperationOutcome)(d)))}):u(c),()=>o=!1},[t,e,u,n]),i}function Q(e,n){if(n){if((0,f.isResource)(n))return n;if((0,f.isReference)(n))return e.getCachedReference(n)}}var P=require("@medplum/core"),S=require("react");var p=require("react");function J(e,n,t={leading:!1}){let[i,r]=(0,p.useState)(e),u=(0,p.useRef)(!1),o=(0,p.useRef)(void 0),c=(0,p.useRef)(!1),d=(0,p.useCallback)(()=>window.clearTimeout(o.current),[]);return(0,p.useEffect)(()=>{u.current&&(!c.current&&t.leading?(c.current=!0,r(e)):(d(),o.current=setTimeout(()=>{c.current=!1,r(e)},n)))},[e,t.leading,n,d]),(0,p.useEffect)(()=>(u.current=!0,d),[d]),[i,d]}var se=250;function ie(e,n,t){return W("search",e,n,t)}function ue(e,n,t){return W("searchOne",e,n,t)}function ce(e,n,t){return W("searchResources",e,n,t)}function W(e,n,t,i){let r=l(),[u,o]=(0,S.useState)(),[c,d]=(0,S.useState)(!0),[b,m]=(0,S.useState)(),[x,h]=(0,S.useState)(),O=r.fhirSearchUrl(n,t).toString(),[R]=J(O,i?.debounceMs??se,{leading:!0});return(0,S.useEffect)(()=>{R!==u&&(o(R),r[e](n,t).then(E=>{d(!1),m(E),h(P.allOk)}).catch(E=>{d(!1),m(void 0),h((0,P.normalizeOperationOutcome)(E))}))},[r,e,n,t,u,R]),[b,c,x]}var w=require("@medplum/core"),s=require("react");var de=3e3;function ae(e,n,t){let i=l(),[r,u]=(0,s.useState)(),[o,c]=(0,s.useState)(t?.subscriptionProps),d=(0,s.useRef)(!1),b=(0,s.useRef)(void 0),m=(0,s.useRef)(void 0),x=(0,s.useRef)(void 0),h=(0,s.useRef)(n);h.current=n;let O=(0,s.useRef)(t?.onWebSocketOpen);O.current=t?.onWebSocketOpen;let R=(0,s.useRef)(t?.onWebSocketClose);R.current=t?.onWebSocketClose;let E=(0,s.useRef)(t?.onSubscriptionConnect);E.current=t?.onSubscriptionConnect;let F=(0,s.useRef)(t?.onSubscriptionDisconnect);F.current=t?.onSubscriptionDisconnect;let B=(0,s.useRef)(t?.onError);B.current=t?.onError,(0,s.useEffect)(()=>{(0,w.deepEquals)(t?.subscriptionProps,o)||c(t?.subscriptionProps)},[o,t]),(0,s.useEffect)(()=>{b.current&&(clearTimeout(b.current),b.current=void 0);let a=!1;return(m.current!==e||!(0,w.deepEquals)(x.current,o))&&(a=!0),a&&m.current&&i.unsubscribeFromCriteria(m.current,x.current),m.current=e,x.current=o,a&&e?u(i.subscribeToCriteria(e,o)):e||u(void 0),()=>{b.current=setTimeout(()=>{u(void 0),e&&i.unsubscribeFromCriteria(e,o)},de)}},[i,e,o]);let L=(0,s.useCallback)(a=>{h.current?.(a.payload)},[]),K=(0,s.useCallback)(()=>{O.current?.()},[]),k=(0,s.useCallback)(()=>{R.current?.()},[]),U=(0,s.useCallback)(a=>{E.current?.(a.payload.subscriptionId)},[]),D=(0,s.useCallback)(a=>{F.current?.(a.payload.subscriptionId)},[]),I=(0,s.useCallback)(a=>{B.current?.(a.payload)},[]);(0,s.useEffect)(()=>r?(d.current||(r.addEventListener("message",L),r.addEventListener("open",K),r.addEventListener("close",k),r.addEventListener("connect",U),r.addEventListener("disconnect",D),r.addEventListener("error",I),d.current=!0),()=>{d.current=!1,r.removeEventListener("message",L),r.removeEventListener("open",K),r.removeEventListener("close",k),r.removeEventListener("connect",U),r.removeEventListener("disconnect",D),r.removeEventListener("error",I)}):()=>{},[r,L,K,k,U,D,I])}
|
|
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/useDebouncedValue/useDebouncedValue.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';\nimport { useDebouncedValue } from '../useDebouncedValue/useDebouncedValue';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\nexport type SearchOptions = { debounceMs?: number };\n\nconst DEFAULT_DEBOUNCE_MS = 250;\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query, options);\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query, options);\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query, options);\n}\n\nfunction useSearchImpl<K extends ResourceType, SearchReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined,\n options?: SearchOptions\n): [SearchReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [lastSearchKey, setLastSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(true);\n const [result, setResult] = useState<SearchReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n const searchKey = medplum.fhirSearchUrl(resourceType, query).toString();\n const [debouncedSearchKey] = useDebouncedValue(searchKey, options?.debounceMs ?? DEFAULT_DEBOUNCE_MS, {\n leading: true,\n });\n\n useEffect(() => {\n if (debouncedSearchKey !== lastSearchKey) {\n setLastSearchKey(debouncedSearchKey);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as SearchReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, lastSearchKey, debouncedSearchKey]);\n\n return [result, loading, outcome];\n}\n", "/*\n This hook was forked from: https://github.com/mantinedev/mantine/blob/fbcee929e0b11782092f48c1e7af2a1d1c878823/packages/%40mantine/hooks/src/use-debounced-value/use-debounced-value.ts\n and has the following license:\n\n MIT License\n\n Copyright (c) 2021 Vitaly Rtishchev\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n*/\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nexport type UseDebouncedValueOptions = {\n /** Whether the first update to `value` should be immediate or not */\n leading?: boolean;\n};\n\n/**\n * This hook allows users to debounce an incoming value by a specified number of milliseconds.\n *\n * Users can also specify whether the first update to `value` in a sequence of rapid updates should be immediate, by specifying `leading: true` in the options.\n * The default value for `leading` is `false`.\n *\n * The return value is a tuple containing the debounced value at `arr[0]` and a function to cancel the pending debounced value change at `arr[1]`.\n *\n * @param value - The value to debounce.\n * @param waitMs - How long in milliseconds should.\n * @param options - Optional options for configuring the debounce.\n * @returns An array tuple of `[debouncedValue, cancelFn]`.\n */\nexport function useDebouncedValue<T = any>(\n value: T,\n waitMs: number,\n options: UseDebouncedValueOptions = { leading: false }\n): [T, () => void] {\n const [debouncedValue, setDebouncedValue] = useState(value);\n const mountedRef = useRef(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>();\n const cooldownRef = useRef(false);\n\n const cancel = useCallback(() => window.clearTimeout(timeoutRef.current), []);\n\n useEffect(() => {\n if (mountedRef.current) {\n if (!cooldownRef.current && options.leading) {\n cooldownRef.current = true;\n setDebouncedValue(value);\n } else {\n cancel();\n timeoutRef.current = setTimeout(() => {\n cooldownRef.current = false;\n setDebouncedValue(value);\n }, waitMs);\n }\n }\n }, [value, options.leading, waitMs, cancel]);\n\n useEffect(() => {\n mountedRef.current = true;\n return cancel;\n }, [cancel]);\n\n return [debouncedValue, cancel] as const;\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": "yaAAA,IAAAA,GAAA,GAAAC,EAAAD,GAAA,qBAAAE,GAAA,iBAAAC,EAAA,uBAAAC,GAAA,eAAAC,EAAA,sBAAAC,EAAA,uBAAAC,EAAA,sBAAAC,EAAA,gBAAAC,GAAA,gBAAAC,GAAA,cAAAC,GAAA,iBAAAC,GAAA,uBAAAC,GAAA,oBAAAC,KAAA,eAAAC,EAAAf,ICCA,IAAAgB,
|
|
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 { JSX, 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>(undefined);\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';\nimport { useDebouncedValue } from '../useDebouncedValue/useDebouncedValue';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\nexport type SearchOptions = { debounceMs?: number };\n\nconst DEFAULT_DEBOUNCE_MS = 250;\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query, options);\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query, options);\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query, options);\n}\n\nfunction useSearchImpl<K extends ResourceType, SearchReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined,\n options?: SearchOptions\n): [SearchReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [lastSearchKey, setLastSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(true);\n const [result, setResult] = useState<SearchReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n const searchKey = medplum.fhirSearchUrl(resourceType, query).toString();\n const [debouncedSearchKey] = useDebouncedValue(searchKey, options?.debounceMs ?? DEFAULT_DEBOUNCE_MS, {\n leading: true,\n });\n\n useEffect(() => {\n if (debouncedSearchKey !== lastSearchKey) {\n setLastSearchKey(debouncedSearchKey);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as SearchReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, lastSearchKey, debouncedSearchKey]);\n\n return [result, loading, outcome];\n}\n", "/*\n This hook was forked from: https://github.com/mantinedev/mantine/blob/fbcee929e0b11782092f48c1e7af2a1d1c878823/packages/%40mantine/hooks/src/use-debounced-value/use-debounced-value.ts\n and has the following license:\n\n MIT License\n\n Copyright (c) 2021 Vitaly Rtishchev\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n*/\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nexport type UseDebouncedValueOptions = {\n /** Whether the first update to `value` should be immediate or not */\n leading?: boolean;\n};\n\n/**\n * This hook allows users to debounce an incoming value by a specified number of milliseconds.\n *\n * Users can also specify whether the first update to `value` in a sequence of rapid updates should be immediate, by specifying `leading: true` in the options.\n * The default value for `leading` is `false`.\n *\n * The return value is a tuple containing the debounced value at `arr[0]` and a function to cancel the pending debounced value change at `arr[1]`.\n *\n * @param value - The value to debounce.\n * @param waitMs - How long in milliseconds should.\n * @param options - Optional options for configuring the debounce.\n * @returns An array tuple of `[debouncedValue, cancelFn]`.\n */\nexport function useDebouncedValue<T = any>(\n value: T,\n waitMs: number,\n options: UseDebouncedValueOptions = { leading: false }\n): [T, () => void] {\n const [debouncedValue, setDebouncedValue] = useState(value);\n const mountedRef = useRef(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n const cooldownRef = useRef(false);\n\n const cancel = useCallback(() => window.clearTimeout(timeoutRef.current), []);\n\n useEffect(() => {\n if (mountedRef.current) {\n if (!cooldownRef.current && options.leading) {\n cooldownRef.current = true;\n setDebouncedValue(value);\n } else {\n cancel();\n timeoutRef.current = setTimeout(() => {\n cooldownRef.current = false;\n setDebouncedValue(value);\n }, waitMs);\n }\n }\n }, [value, options.leading, waitMs, cancel]);\n\n useEffect(() => {\n mountedRef.current = true;\n return cancel;\n }, [cancel]);\n\n return [debouncedValue, cancel] as const;\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>>(undefined);\n\n const prevCriteriaRef = useRef<string | undefined>(undefined);\n const prevMemoizedSubPropsRef = useRef<UseSubscriptionOptions['subscriptionProps']>(undefined);\n\n const callbackRef = useRef<typeof callback>(callback);\n callbackRef.current = callback;\n\n const onWebSocketOpenRef = useRef<UseSubscriptionOptions['onWebSocketOpen']>(options?.onWebSocketOpen);\n onWebSocketOpenRef.current = options?.onWebSocketOpen;\n\n const onWebSocketCloseRef = useRef<UseSubscriptionOptions['onWebSocketClose']>(options?.onWebSocketClose);\n onWebSocketCloseRef.current = options?.onWebSocketClose;\n\n const onSubscriptionConnectRef = useRef<UseSubscriptionOptions['onSubscriptionConnect']>(\n options?.onSubscriptionConnect\n );\n onSubscriptionConnectRef.current = options?.onSubscriptionConnect;\n\n const onSubscriptionDisconnectRef = useRef<UseSubscriptionOptions['onSubscriptionDisconnect']>(\n options?.onSubscriptionDisconnect\n );\n onSubscriptionDisconnectRef.current = options?.onSubscriptionDisconnect;\n\n const onErrorRef = useRef<UseSubscriptionOptions['onError']>(options?.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": ["index_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", "import_react", "useDebouncedValue", "value", "waitMs", "options", "debouncedValue", "setDebouncedValue", "mountedRef", "timeoutRef", "cooldownRef", "cancel", "DEFAULT_DEBOUNCE_MS", "useSearch", "resourceType", "query", "options", "useSearchImpl", "useSearchOne", "useSearchResources", "searchFn", "medplum", "useMedplum", "lastSearchKey", "setLastSearchKey", "loading", "setLoading", "result", "setResult", "outcome", "setOutcome", "searchKey", "debouncedSearchKey", "useDebouncedValue", "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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Bundle } from '@medplum/fhirtypes';
|
|
2
2
|
import { Context } from 'react';
|
|
3
3
|
import { ExtractResource } from '@medplum/fhirtypes';
|
|
4
|
+
import { JSX } from 'react';
|
|
4
5
|
import { MedplumClient } from '@medplum/core';
|
|
5
6
|
import { OperationOutcome } from '@medplum/fhirtypes';
|
|
6
7
|
import { ProfileResource } from '@medplum/core';
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Bundle } from '@medplum/fhirtypes';
|
|
2
2
|
import { Context } from 'react';
|
|
3
3
|
import { ExtractResource } from '@medplum/fhirtypes';
|
|
4
|
+
import { JSX } from 'react';
|
|
4
5
|
import { MedplumClient } from '@medplum/core';
|
|
5
6
|
import { OperationOutcome } from '@medplum/fhirtypes';
|
|
6
7
|
import { ProfileResource } from '@medplum/core';
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useEffect as Q,useMemo as J,useState as X}from"react";import{createContext as z,useContext as A}from"react";var y=z(void 0);function P(){return A(y)}function
|
|
1
|
+
import{useEffect as Q,useMemo as J,useState as X}from"react";import{createContext as z,useContext as A}from"react";var y=z(void 0);function P(){return A(y)}function S(){return P().medplum}function fe(){return P().navigate}function me(){return P().profile}import{jsx as G}from"react/jsx-runtime";var I=["change","storageInitialized","storageInitFailed","profileRefreshing","profileRefreshed"];function ve(e){let n=e.medplum,t=e.navigate??q,[s,r]=X({profile:n.getProfile(),loading:n.isLoading()});Q(()=>{function o(){r(u=>({...u,profile:n.getProfile(),loading:n.isLoading()}))}for(let u of I)n.addEventListener(u,o);return()=>{for(let u of I)n.removeEventListener(u,o)}},[n]);let i=J(()=>({...s,medplum:n,navigate:t}),[s,n,t]);return G(y.Provider,{value:i,children:e.children})}function q(e){window.location.assign(e)}import{useMemo as H}from"react";var N=new Map,Ce=e=>H(()=>{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=N.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 N.set(n,e),e},[e]);import{useEffect as Y,useRef as Z}from"react";function ye(e){let n=Z(void 0);return Y(()=>{n.current=e}),n.current}import{deepEquals as $,isReference as w,isResource as j,normalizeOperationOutcome as ee}from"@medplum/core";import{useCallback as ne,useEffect as te,useState as re}from"react";function De(e,n){let t=S(),[s,r]=re(()=>W(t,e)),i=ne(o=>{$(o,s)||r(o)},[s]);return te(()=>{let o=!0,u=W(t,e);return!u&&w(e)?t.readReference(e).then(c=>{o&&i(c)}).catch(c=>{o&&(i(void 0),n&&n(ee(c)))}):i(u),()=>o=!1},[t,e,i,n]),s}function W(e,n){if(n){if(j(n))return n;if(w(n))return e.getCachedReference(n)}}import{allOk as ie,normalizeOperationOutcome as ue}from"@medplum/core";import{useEffect as ce,useState as v}from"react";import{useCallback as oe,useEffect as F,useRef as L,useState as se}from"react";function B(e,n,t={leading:!1}){let[s,r]=se(e),i=L(!1),o=L(void 0),u=L(!1),c=oe(()=>window.clearTimeout(o.current),[]);return F(()=>{i.current&&(!u.current&&t.leading?(u.current=!0,r(e)):(c(),o.current=setTimeout(()=>{u.current=!1,r(e)},n)))},[e,t.leading,n,c]),F(()=>(i.current=!0,c),[c]),[s,c]}var de=250;function Ae(e,n,t){return K("search",e,n,t)}function Qe(e,n,t){return K("searchOne",e,n,t)}function Je(e,n,t){return K("searchResources",e,n,t)}function K(e,n,t,s){let r=S(),[i,o]=v(),[u,c]=v(!0),[f,p]=v(),[R,E]=v(),g=r.fhirSearchUrl(n,t).toString(),[m]=B(g,s?.debounceMs??de,{leading:!0});return ce(()=>{m!==i&&(o(m),r[e](n,t).then(l=>{c(!1),p(l),E(ie)}).catch(l=>{c(!1),p(void 0),E(ue(l))}))},[r,e,n,t,i,m]),[f,u,R]}import{deepEquals as V}from"@medplum/core";import{useCallback as b,useEffect as k,useRef as a,useState as _}from"react";var ae=3e3;function $e(e,n,t){let s=S(),[r,i]=_(),[o,u]=_(t?.subscriptionProps),c=a(!1),f=a(void 0),p=a(void 0),R=a(void 0),E=a(n);E.current=n;let g=a(t?.onWebSocketOpen);g.current=t?.onWebSocketOpen;let m=a(t?.onWebSocketClose);m.current=t?.onWebSocketClose;let l=a(t?.onSubscriptionConnect);l.current=t?.onSubscriptionConnect;let U=a(t?.onSubscriptionDisconnect);U.current=t?.onSubscriptionDisconnect;let D=a(t?.onError);D.current=t?.onError,k(()=>{V(t?.subscriptionProps,o)||u(t?.subscriptionProps)},[o,t]),k(()=>{f.current&&(clearTimeout(f.current),f.current=void 0);let d=!1;return(p.current!==e||!V(R.current,o))&&(d=!0),d&&p.current&&s.unsubscribeFromCriteria(p.current,R.current),p.current=e,R.current=o,d&&e?i(s.subscribeToCriteria(e,o)):e||i(void 0),()=>{f.current=setTimeout(()=>{i(void 0),e&&s.unsubscribeFromCriteria(e,o)},ae)}},[s,e,o]);let x=b(d=>{E.current?.(d.payload)},[]),h=b(()=>{g.current?.()},[]),O=b(()=>{m.current?.()},[]),C=b(d=>{l.current?.(d.payload.subscriptionId)},[]),T=b(d=>{U.current?.(d.payload.subscriptionId)},[]),M=b(d=>{D.current?.(d.payload)},[]);k(()=>r?(c.current||(r.addEventListener("message",x),r.addEventListener("open",h),r.addEventListener("close",O),r.addEventListener("connect",C),r.addEventListener("disconnect",T),r.addEventListener("error",M),c.current=!0),()=>{c.current=!1,r.removeEventListener("message",x),r.removeEventListener("open",h),r.removeEventListener("close",O),r.removeEventListener("connect",C),r.removeEventListener("disconnect",T),r.removeEventListener("error",M)}):()=>{},[r,x,h,O,C,T,M])}export{ve as MedplumProvider,y as reactContext,Ce as useCachedBinaryUrl,S as useMedplum,P as useMedplumContext,fe as useMedplumNavigate,me as useMedplumProfile,ye as usePrevious,De as useResource,Ae as useSearch,Qe as useSearchOne,Je as useSearchResources,$e 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/useDebouncedValue/useDebouncedValue.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';\nimport { useDebouncedValue } from '../useDebouncedValue/useDebouncedValue';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\nexport type SearchOptions = { debounceMs?: number };\n\nconst DEFAULT_DEBOUNCE_MS = 250;\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query, options);\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query, options);\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query, options);\n}\n\nfunction useSearchImpl<K extends ResourceType, SearchReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined,\n options?: SearchOptions\n): [SearchReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [lastSearchKey, setLastSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(true);\n const [result, setResult] = useState<SearchReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n const searchKey = medplum.fhirSearchUrl(resourceType, query).toString();\n const [debouncedSearchKey] = useDebouncedValue(searchKey, options?.debounceMs ?? DEFAULT_DEBOUNCE_MS, {\n leading: true,\n });\n\n useEffect(() => {\n if (debouncedSearchKey !== lastSearchKey) {\n setLastSearchKey(debouncedSearchKey);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as SearchReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, lastSearchKey, debouncedSearchKey]);\n\n return [result, loading, outcome];\n}\n", "/*\n This hook was forked from: https://github.com/mantinedev/mantine/blob/fbcee929e0b11782092f48c1e7af2a1d1c878823/packages/%40mantine/hooks/src/use-debounced-value/use-debounced-value.ts\n and has the following license:\n\n MIT License\n\n Copyright (c) 2021 Vitaly Rtishchev\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n*/\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nexport type UseDebouncedValueOptions = {\n /** Whether the first update to `value` should be immediate or not */\n leading?: boolean;\n};\n\n/**\n * This hook allows users to debounce an incoming value by a specified number of milliseconds.\n *\n * Users can also specify whether the first update to `value` in a sequence of rapid updates should be immediate, by specifying `leading: true` in the options.\n * The default value for `leading` is `false`.\n *\n * The return value is a tuple containing the debounced value at `arr[0]` and a function to cancel the pending debounced value change at `arr[1]`.\n *\n * @param value - The value to debounce.\n * @param waitMs - How long in milliseconds should.\n * @param options - Optional options for configuring the debounce.\n * @returns An array tuple of `[debouncedValue, cancelFn]`.\n */\nexport function useDebouncedValue<T = any>(\n value: T,\n waitMs: number,\n options: UseDebouncedValueOptions = { leading: false }\n): [T, () => void] {\n const [debouncedValue, setDebouncedValue] = useState(value);\n const mountedRef = useRef(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>();\n const cooldownRef = useRef(false);\n\n const cancel = useCallback(() => window.clearTimeout(timeoutRef.current), []);\n\n useEffect(() => {\n if (mountedRef.current) {\n if (!cooldownRef.current && options.leading) {\n cooldownRef.current = true;\n setDebouncedValue(value);\n } else {\n cancel();\n timeoutRef.current = setTimeout(() => {\n cooldownRef.current = false;\n setDebouncedValue(value);\n }, waitMs);\n }\n }\n }, [value, options.leading, waitMs, cancel]);\n\n useEffect(() => {\n mountedRef.current = true;\n return cancel;\n }, [cancel]);\n\n return [debouncedValue, cancel] as const;\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": "AACA,
|
|
4
|
+
"sourcesContent": ["import { MedplumClient, MedplumClientEventMap } from '@medplum/core';\nimport { JSX, 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>(undefined);\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';\nimport { useDebouncedValue } from '../useDebouncedValue/useDebouncedValue';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\nexport type SearchOptions = { debounceMs?: number };\n\nconst DEFAULT_DEBOUNCE_MS = 250;\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query, options);\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query, options);\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 * @param options - Optional options for configuring the search.\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 options?: SearchOptions\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query, options);\n}\n\nfunction useSearchImpl<K extends ResourceType, SearchReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined,\n options?: SearchOptions\n): [SearchReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [lastSearchKey, setLastSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(true);\n const [result, setResult] = useState<SearchReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n const searchKey = medplum.fhirSearchUrl(resourceType, query).toString();\n const [debouncedSearchKey] = useDebouncedValue(searchKey, options?.debounceMs ?? DEFAULT_DEBOUNCE_MS, {\n leading: true,\n });\n\n useEffect(() => {\n if (debouncedSearchKey !== lastSearchKey) {\n setLastSearchKey(debouncedSearchKey);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as SearchReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, lastSearchKey, debouncedSearchKey]);\n\n return [result, loading, outcome];\n}\n", "/*\n This hook was forked from: https://github.com/mantinedev/mantine/blob/fbcee929e0b11782092f48c1e7af2a1d1c878823/packages/%40mantine/hooks/src/use-debounced-value/use-debounced-value.ts\n and has the following license:\n\n MIT License\n\n Copyright (c) 2021 Vitaly Rtishchev\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in all\n copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n SOFTWARE.\n*/\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nexport type UseDebouncedValueOptions = {\n /** Whether the first update to `value` should be immediate or not */\n leading?: boolean;\n};\n\n/**\n * This hook allows users to debounce an incoming value by a specified number of milliseconds.\n *\n * Users can also specify whether the first update to `value` in a sequence of rapid updates should be immediate, by specifying `leading: true` in the options.\n * The default value for `leading` is `false`.\n *\n * The return value is a tuple containing the debounced value at `arr[0]` and a function to cancel the pending debounced value change at `arr[1]`.\n *\n * @param value - The value to debounce.\n * @param waitMs - How long in milliseconds should.\n * @param options - Optional options for configuring the debounce.\n * @returns An array tuple of `[debouncedValue, cancelFn]`.\n */\nexport function useDebouncedValue<T = any>(\n value: T,\n waitMs: number,\n options: UseDebouncedValueOptions = { leading: false }\n): [T, () => void] {\n const [debouncedValue, setDebouncedValue] = useState(value);\n const mountedRef = useRef(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n const cooldownRef = useRef(false);\n\n const cancel = useCallback(() => window.clearTimeout(timeoutRef.current), []);\n\n useEffect(() => {\n if (mountedRef.current) {\n if (!cooldownRef.current && options.leading) {\n cooldownRef.current = true;\n setDebouncedValue(value);\n } else {\n cancel();\n timeoutRef.current = setTimeout(() => {\n cooldownRef.current = false;\n setDebouncedValue(value);\n }, waitMs);\n }\n }\n }, [value, options.leading, waitMs, cancel]);\n\n useEffect(() => {\n mountedRef.current = true;\n return cancel;\n }, [cancel]);\n\n return [debouncedValue, cancel] as const;\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>>(undefined);\n\n const prevCriteriaRef = useRef<string | undefined>(undefined);\n const prevMemoizedSubPropsRef = useRef<UseSubscriptionOptions['subscriptionProps']>(undefined);\n\n const callbackRef = useRef<typeof callback>(callback);\n callbackRef.current = callback;\n\n const onWebSocketOpenRef = useRef<UseSubscriptionOptions['onWebSocketOpen']>(options?.onWebSocketOpen);\n onWebSocketOpenRef.current = options?.onWebSocketOpen;\n\n const onWebSocketCloseRef = useRef<UseSubscriptionOptions['onWebSocketClose']>(options?.onWebSocketClose);\n onWebSocketCloseRef.current = options?.onWebSocketClose;\n\n const onSubscriptionConnectRef = useRef<UseSubscriptionOptions['onSubscriptionConnect']>(\n options?.onSubscriptionConnect\n );\n onSubscriptionConnectRef.current = options?.onSubscriptionConnect;\n\n const onSubscriptionDisconnectRef = useRef<UseSubscriptionOptions['onSubscriptionDisconnect']>(\n options?.onSubscriptionDisconnect\n );\n onSubscriptionDisconnectRef.current = options?.onSubscriptionDisconnect;\n\n const onErrorRef = useRef<UseSubscriptionOptions['onError']>(options?.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": "AACA,OAAyB,aAAAA,EAAW,WAAAC,EAAS,YAAAC,MAAgB,QCA7D,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,MAAS,EAC/B,OAAAD,EAAU,IAAM,CACdI,EAAI,QAAUD,CAChB,CAAC,EACMC,EAAI,OACb,CCbA,OAAS,cAAAC,EAAY,eAAAC,EAAa,cAAAC,EAA2B,6BAAAC,OAAiC,gBAE9F,OAAS,eAAAC,GAAa,aAAAC,GAAW,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,GAC1BC,GAAqB,CACfC,EAAWD,EAAGN,CAAQ,GACzBC,EAAYK,CAAC,CAEjB,EACA,CAACN,CAAQ,CACX,EAEA,OAAAQ,GAAU,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,GAA0BD,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,QCyBpC,OAAS,eAAAC,GAAa,aAAAC,EAAW,UAAAC,EAAQ,YAAAC,OAAgB,QAoBlD,SAASC,EACdC,EACAC,EACAC,EAAoC,CAAE,QAAS,EAAM,EACpC,CACjB,GAAM,CAACC,EAAgBC,CAAiB,EAAIN,GAASE,CAAK,EACpDK,EAAaR,EAAO,EAAK,EACzBS,EAAaT,EAAsC,MAAS,EAC5DU,EAAcV,EAAO,EAAK,EAE1BW,EAASb,GAAY,IAAM,OAAO,aAAaW,EAAW,OAAO,EAAG,CAAC,CAAC,EAE5E,OAAAV,EAAU,IAAM,CACVS,EAAW,UACT,CAACE,EAAY,SAAWL,EAAQ,SAClCK,EAAY,QAAU,GACtBH,EAAkBJ,CAAK,IAEvBQ,EAAO,EACPF,EAAW,QAAU,WAAW,IAAM,CACpCC,EAAY,QAAU,GACtBH,EAAkBJ,CAAK,CACzB,EAAGC,CAAM,GAGf,EAAG,CAACD,EAAOE,EAAQ,QAASD,EAAQO,CAAM,CAAC,EAE3CZ,EAAU,KACRS,EAAW,QAAU,GACdG,GACN,CAACA,CAAM,CAAC,EAEJ,CAACL,EAAgBK,CAAM,CAChC,CDvEA,IAAMC,GAAsB,IAYrB,SAASC,GACdC,EACAC,EACAC,EACiF,CACjF,OAAOC,EAA6C,SAAUH,EAAcC,EAAOC,CAAO,CAC5F,CAYO,SAASE,GACdJ,EACAC,EACAC,EACyE,CACzE,OAAOC,EAAqC,YAAaH,EAAcC,EAAOC,CAAO,CACvF,CAYO,SAASG,GACdL,EACAC,EACAC,EACwF,CACxF,OAAOC,EAAoD,kBAAmBH,EAAcC,EAAOC,CAAO,CAC5G,CAEA,SAASC,EACPG,EACAN,EACAC,EACAC,EACuE,CACvE,IAAMK,EAAUC,EAAW,EACrB,CAACC,EAAeC,CAAgB,EAAIC,EAAiB,EACrD,CAACC,EAASC,CAAU,EAAIF,EAAkB,EAAI,EAC9C,CAACG,EAAQC,CAAS,EAAIJ,EAA2B,EACjD,CAACK,EAASC,CAAU,EAAIN,EAA2B,EAEnDO,EAAYX,EAAQ,cAAcP,EAAcC,CAAK,EAAE,SAAS,EAChE,CAACkB,CAAkB,EAAIC,EAAkBF,EAAWhB,GAAS,YAAcJ,GAAqB,CACpG,QAAS,EACX,CAAC,EAED,OAAAuB,GAAU,IAAM,CACVF,IAAuBV,IACzBC,EAAiBS,CAAkB,EACnCZ,EAAQD,CAAQ,EAAEN,EAAcC,CAAK,EAClC,KAAMqB,GAAQ,CACbT,EAAW,EAAK,EAChBE,EAAUO,CAAuB,EACjCL,EAAWM,EAAK,CAClB,CAAC,EACA,MAAOC,GAAQ,CACdX,EAAW,EAAK,EAChBE,EAAU,MAAS,EACnBE,EAAWQ,GAA0BD,CAAG,CAAC,CAC3C,CAAC,EAEP,EAAG,CAACjB,EAASD,EAAUN,EAAcC,EAAOQ,EAAeU,CAAkB,CAAC,EAEvE,CAACL,EAAQF,EAASI,CAAO,CAClC,CEpGA,OAAoD,cAAAU,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,MAAS,EAE/DE,EAAkBF,EAA2B,MAAS,EACtDG,EAA0BH,EAAoD,MAAS,EAEvFI,EAAcJ,EAAwBV,CAAQ,EACpDc,EAAY,QAAUd,EAEtB,IAAMe,EAAqBL,EAAkDT,GAAS,eAAe,EACrGc,EAAmB,QAAUd,GAAS,gBAEtC,IAAMe,EAAsBN,EAAmDT,GAAS,gBAAgB,EACxGe,EAAoB,QAAUf,GAAS,iBAEvC,IAAMgB,EAA2BP,EAC/BT,GAAS,qBACX,EACAgB,EAAyB,QAAUhB,GAAS,sBAE5C,IAAMiB,EAA8BR,EAClCT,GAAS,wBACX,EACAiB,EAA4B,QAAUjB,GAAS,yBAE/C,IAAMkB,EAAaT,EAA0CT,GAAS,OAAO,EAC7EkB,EAAW,QAAUlB,GAAS,QAE9BmB,EAAU,IAAM,CAETC,EAAWpB,GAAS,kBAAmBM,CAAgB,GAC1DC,EAAoBP,GAAS,iBAAiB,CAElD,EAAG,CAACM,EAAkBN,CAAO,CAAC,EAE9BmB,EAAU,IAAM,CACVT,EAAc,UAChB,aAAaA,EAAc,OAAO,EAClCA,EAAc,QAAU,QAG1B,IAAIW,EAAkB,GACtB,OAAIV,EAAgB,UAAYb,GAAY,CAACsB,EAAWR,EAAwB,QAASN,CAAgB,KACvGe,EAAkB,IAGhBA,GAAmBV,EAAgB,SACrCV,EAAQ,wBAAwBU,EAAgB,QAASC,EAAwB,OAAO,EAI1FD,EAAgB,QAAUb,EAC1Bc,EAAwB,QAAUN,EAG9Be,GAAmBvB,EACrBM,EAAWH,EAAQ,oBAAoBH,EAAUQ,CAAgB,CAAC,EACxDR,GACVM,EAAW,MAAS,EAGf,IAAM,CACXM,EAAc,QAAU,WAAW,IAAM,CACvCN,EAAW,MAAS,EAChBN,GACFG,EAAQ,wBAAwBH,EAAUQ,CAAgB,CAE9D,EAAGV,EAAwB,CAC7B,CACF,EAAG,CAACK,EAASH,EAAUQ,CAAgB,CAAC,EAExC,IAAMgB,EAAkBC,EAAaC,GAA2C,CAC9EX,EAAY,UAAUW,EAAM,OAAO,CACrC,EAAG,CAAC,CAAC,EAECC,EAAkBF,EAAY,IAAM,CACxCT,EAAmB,UAAU,CAC/B,EAAG,CAAC,CAAC,EAECY,EAAmBH,EAAY,IAAM,CACzCR,EAAoB,UAAU,CAChC,EAAG,CAAC,CAAC,EAECY,EAAwBJ,EAAaC,GAA2C,CACpFR,EAAyB,UAAUQ,EAAM,QAAQ,cAAc,CACjE,EAAG,CAAC,CAAC,EAECI,EAA2BL,EAAaC,GAA8C,CAC1FP,EAA4B,UAAUO,EAAM,QAAQ,cAAc,CACpE,EAAG,CAAC,CAAC,EAECK,EAAUN,EAAaC,GAAyC,CACpEN,EAAW,UAAUM,EAAM,OAAO,CACpC,EAAG,CAAC,CAAC,EAELL,EAAU,IACHhB,GAGAK,EAAa,UAChBL,EAAQ,iBAAiB,UAAWmB,CAAe,EACnDnB,EAAQ,iBAAiB,OAAQsB,CAAe,EAChDtB,EAAQ,iBAAiB,QAASuB,CAAgB,EAClDvB,EAAQ,iBAAiB,UAAWwB,CAAqB,EACzDxB,EAAQ,iBAAiB,aAAcyB,CAAwB,EAC/DzB,EAAQ,iBAAiB,QAAS0B,CAAO,EACzCrB,EAAa,QAAU,IAElB,IAAM,CACXA,EAAa,QAAU,GACvBL,EAAQ,oBAAoB,UAAWmB,CAAe,EACtDnB,EAAQ,oBAAoB,OAAQsB,CAAe,EACnDtB,EAAQ,oBAAoB,QAASuB,CAAgB,EACrDvB,EAAQ,oBAAoB,UAAWwB,CAAqB,EAC5DxB,EAAQ,oBAAoB,aAAcyB,CAAwB,EAClEzB,EAAQ,oBAAoB,QAAS0B,CAAO,CAC9C,GAnBS,IAAG,GAoBX,CACD1B,EACAmB,EACAG,EACAC,EACAC,EACAC,EACAC,CACF,CAAC,CACH",
|
|
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", "useCallback", "useEffect", "useRef", "useState", "useDebouncedValue", "value", "waitMs", "options", "debouncedValue", "setDebouncedValue", "mountedRef", "timeoutRef", "cooldownRef", "cancel", "DEFAULT_DEBOUNCE_MS", "useSearch", "resourceType", "query", "options", "useSearchImpl", "useSearchOne", "useSearchResources", "searchFn", "medplum", "useMedplum", "lastSearchKey", "setLastSearchKey", "useState", "loading", "setLoading", "result", "setResult", "outcome", "setOutcome", "searchKey", "debouncedSearchKey", "useDebouncedValue", "useEffect", "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": "4.1.
|
|
3
|
+
"version": "4.1.5",
|
|
4
4
|
"description": "Medplum React Hooks Library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"medplum",
|
|
@@ -58,26 +58,26 @@
|
|
|
58
58
|
"test": "jest"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@medplum/core": "4.1.
|
|
62
|
-
"@medplum/definitions": "4.1.
|
|
63
|
-
"@medplum/fhirtypes": "4.1.
|
|
64
|
-
"@medplum/mock": "4.1.
|
|
61
|
+
"@medplum/core": "4.1.5",
|
|
62
|
+
"@medplum/definitions": "4.1.5",
|
|
63
|
+
"@medplum/fhirtypes": "4.1.5",
|
|
64
|
+
"@medplum/mock": "4.1.5",
|
|
65
65
|
"@testing-library/dom": "10.4.0",
|
|
66
66
|
"@testing-library/jest-dom": "6.6.3",
|
|
67
67
|
"@testing-library/react": "16.3.0",
|
|
68
68
|
"@types/jest": "29.5.14",
|
|
69
|
-
"@types/node": "20.17.
|
|
70
|
-
"@types/react": "
|
|
71
|
-
"@types/react-dom": "
|
|
69
|
+
"@types/node": "20.17.47",
|
|
70
|
+
"@types/react": "19.1.4",
|
|
71
|
+
"@types/react-dom": "19.1.5",
|
|
72
72
|
"jest": "29.7.0",
|
|
73
73
|
"jest-each": "29.7.0",
|
|
74
74
|
"jest-websocket-mock": "2.5.0",
|
|
75
|
-
"react": "
|
|
76
|
-
"react-dom": "
|
|
75
|
+
"react": "19.1.0",
|
|
76
|
+
"react-dom": "19.1.0",
|
|
77
77
|
"typescript": "5.8.3"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@medplum/core": "4.1.
|
|
80
|
+
"@medplum/core": "4.1.5",
|
|
81
81
|
"react": "^17.0.2 || ^18.0.0 || ^19.0.0"
|
|
82
82
|
},
|
|
83
83
|
"engines": {
|